index.vue 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. <template>
  2. <div class="hc-context-menu-box" :class="ui" :id="uuid" v-click-outside="onClickOutside">
  3. <template v-for="item in menus">
  4. <div class="hc-context-menu-item" @click.stop="optionClicked(item)">
  5. <slot :name='item.key' :item="item" v-if="item.isSlot"/>
  6. <template v-else>
  7. <HcIcon :name="item.icon" class="menu-item-icon" v-if="item.icon"/>
  8. <span class="menu-item-name">{{item.label}}</span>
  9. </template>
  10. </div>
  11. </template>
  12. </div>
  13. </template>
  14. <script setup>
  15. import {ref, useSlots, watch, nextTick, onMounted, onBeforeUnmount} from "vue";
  16. import clickOutsideVue from 'click-outside-vue3';
  17. import {getRandom, deepClone} from "vue-utils-plus"
  18. const props = defineProps({
  19. ui: {
  20. type: String,
  21. default: ''
  22. },
  23. datas: {
  24. type: Array,
  25. default: () => ([])
  26. }
  27. })
  28. //初始变量
  29. const uuid = getRandom()
  30. const menus = ref(props.datas)
  31. //挂载自定义指令
  32. const vClickOutside = clickOutsideVue.directive
  33. //监听表头
  34. watch(() => [
  35. props.datas
  36. ], ([datas]) => {
  37. setIsSlots(datas)
  38. })
  39. //加载完成
  40. nextTick(()=>{
  41. setIsSlots(props.datas)
  42. })
  43. //渲染完成
  44. onMounted(()=>{
  45. document.body.addEventListener('keyup', onEscKeyRelease);
  46. })
  47. //判断<slot>是否有传值
  48. const slots = useSlots()
  49. const setIsSlots = (datas) => {
  50. let arr = deepClone(datas)
  51. for (let i = 0; i < arr.length; i++) {
  52. arr[i].isSlot = !!slots[arr[i].key]
  53. }
  54. menus.value = arr
  55. }
  56. //显示菜单
  57. const showMenu = (event) => {
  58. let menu = document.getElementById(uuid);
  59. if (!menu) return;
  60. //取宽高
  61. menu.style.visibility = 'hidden';
  62. menu.style.display = 'block';
  63. let menuWidth = menu.offsetWidth;
  64. let menuHeight = menu.offsetHeight;
  65. menu.removeAttribute('style');
  66. //宽
  67. if (menuWidth + event.pageX >= window.innerWidth) {
  68. menu.style.left = event.pageX - menuWidth + 2 + 'px';
  69. } else {
  70. menu.style.left = event.pageX - 2 + 'px';
  71. }
  72. //高
  73. if (menuHeight + event.pageY >= window.innerHeight) {
  74. menu.style.top = event.pageY - menuHeight + 2 + 'px';
  75. } else {
  76. menu.style.top = event.pageY - 2 + 'px';
  77. }
  78. menu.classList.add('active');
  79. }
  80. //事件
  81. const emit = defineEmits(['closed', 'item-click'])
  82. const hideContextMenu = () => {
  83. const element = document.getElementById(uuid);
  84. if (element) {
  85. element.classList.remove('active');
  86. emit('closed')
  87. }
  88. }
  89. const onClickOutside = () => {
  90. console.log(111)
  91. hideContextMenu()
  92. }
  93. //菜单被点击
  94. const optionClicked = (item) => {
  95. hideContextMenu()
  96. emit('item-click', item)
  97. }
  98. const onEscKeyRelease = (event) => {
  99. if (event.keyCode === 27) {
  100. hideContextMenu()
  101. }
  102. }
  103. //卸载之前
  104. onBeforeUnmount(() => {
  105. document.removeEventListener('keyup', onEscKeyRelease);
  106. })
  107. // 暴露出去
  108. defineExpose({
  109. showMenu
  110. })
  111. </script>
  112. <style lang="scss" scoped>
  113. .hc-context-menu-box {
  114. position: fixed;
  115. background-color: #fff;
  116. border-radius: 4px;
  117. width: max-content;
  118. display: none;
  119. left: 0;
  120. top: 0;
  121. box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
  122. z-index: 1000000;
  123. &.active {
  124. display: block;
  125. }
  126. .hc-context-menu-item {
  127. align-items: center;
  128. padding: 5px 15px;
  129. display: flex;
  130. cursor: pointer;
  131. position: relative;
  132. height: 40px;
  133. font-size: 16px;
  134. color: #333639;
  135. .menu-item-icon {
  136. margin-right: 6px;
  137. }
  138. &:hover {
  139. background-color: #f3f3f5;
  140. }
  141. &:first-of-type {
  142. margin-top: 4px;
  143. }
  144. &:last-of-type {
  145. margin-bottom: 4px;
  146. }
  147. }
  148. }
  149. </style>
  150. <style lang="scss">
  151. .hc-context-menu-box .hc-context-menu-item .menu-item-icon {
  152. margin-right: 6px;
  153. }
  154. </style>