hover-operate.vue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. <template>
  2. <Teleport :to="`#${DomMountId}`" v-if="stage">
  3. <transition name="pointer-fade">
  4. <div
  5. v-if="pointer"
  6. :style="{ transform: pointer }"
  7. :size="8"
  8. class="propertys-controller"
  9. ref="layout"
  10. >
  11. <ElMenu>
  12. <ElMenuItem v-for="menu in menus" @click="clickHandler(menu.handler)">
  13. <span class="menu-item">{{ menu.label }}</span>
  14. </ElMenuItem>
  15. </ElMenu>
  16. </div>
  17. </transition>
  18. </Teleport>
  19. </template>
  20. <script lang="ts" setup>
  21. import { computed, nextTick, ref, watch } from "vue";
  22. import { useMouseMenusFilter, useStage } from "../../hook/use-global-vars.ts";
  23. import { useMode } from "../../hook/use-status.ts";
  24. import { DC, EntityShape } from "@/deconstruction.js";
  25. import { useViewerTransformConfig } from "../../hook/use-viewer.ts";
  26. import { Transform } from "konva/lib/Util";
  27. import { DomMountId } from "@/constant/index.ts";
  28. import { ElMenu, ElMenuItem } from "element-plus";
  29. import { Mode } from "@/constant/mode.ts";
  30. import { useGlobalOnlyRightClickShape } from "@/core/hook/use-event.ts";
  31. import { useStore } from "@/core/store/index.ts";
  32. const props = defineProps<{
  33. target: DC<EntityShape> | undefined;
  34. data?: Record<string, any>;
  35. menus: Array<{ icon?: any; label?: string; handler: () => void }>;
  36. }>();
  37. const emit = defineEmits<{
  38. (e: "show" | "hide"): void;
  39. }>();
  40. const layout = ref<HTMLDivElement>();
  41. const stage = useStage();
  42. const { getFilter } = useMouseMenusFilter();
  43. const store = useStore();
  44. const id = computed(() => props.target?.getNode().id());
  45. const type = computed(() => id.value && store.getType(id.value));
  46. const menus = computed(() => {
  47. if (id.value && type.value) {
  48. return getFilter(type.value, id.value)(props.menus);
  49. } else {
  50. return props.menus;
  51. }
  52. });
  53. const show = ref(false);
  54. const { add } = useGlobalOnlyRightClickShape();
  55. const mode = useMode();
  56. watch(
  57. () => !mode.value.has(Mode.draw) && props.target?.getNode(),
  58. (shape, _, onCleanup) => {
  59. if (!shape) return;
  60. const del = add(
  61. shape,
  62. async () => {
  63. await nextTick();
  64. show.value = true;
  65. },
  66. () => {
  67. show.value = false;
  68. }
  69. );
  70. onCleanup(del);
  71. }
  72. );
  73. const clickHandler = (handler: () => void) => {
  74. handler();
  75. show.value = false;
  76. };
  77. const move = new Transform();
  78. const pointer = ref<string | null>(null);
  79. const calcPointer = async () => {
  80. if (!show.value) {
  81. pointer.value = null;
  82. return;
  83. } else if (pointer.value) {
  84. return;
  85. }
  86. const $stage = stage.value!.getStage();
  87. const mousePosition = $stage.pointerPos;
  88. if (!mousePosition) {
  89. pointer.value = null;
  90. return;
  91. }
  92. const $shape = props.target!.getNode();
  93. const shapeRect = $shape.getClientRect();
  94. const shapeR = shapeRect.x + shapeRect.width;
  95. const shapeB = shapeRect.y + shapeRect.height;
  96. let x = Math.min(Math.max(mousePosition.x, shapeRect.x), shapeR);
  97. let y = Math.min(Math.max(mousePosition.y, shapeRect.y), shapeB);
  98. move.reset();
  99. move.translate(x, y);
  100. pointer.value = `matrix(${move.m.join(",")})`;
  101. await nextTick();
  102. const domRect = layout.value!.getBoundingClientRect();
  103. x = x - domRect.width / 2;
  104. x = Math.max(x, shapeRect.x);
  105. if (x + domRect.width > shapeR) {
  106. x = shapeR - domRect.width;
  107. }
  108. if (y + domRect.height > shapeB) {
  109. y = y - domRect.height;
  110. }
  111. x = Math.max(x, 10);
  112. y = Math.max(y, 10);
  113. if (x + domRect.width > $stage.width() - 10) {
  114. x = $stage.width() - domRect.width - 10;
  115. }
  116. if (y + domRect.height > $stage.height() - 10) {
  117. y = $stage.height() - domRect.height - 10;
  118. }
  119. move.reset();
  120. move.translate(x, y);
  121. pointer.value = `matrix(${move.m.join(",")})`;
  122. };
  123. watch(
  124. () => !!pointer.value,
  125. (show) => {
  126. emit(show ? "show" : "hide");
  127. }
  128. );
  129. let timeout: any;
  130. const resetPointer = () => {
  131. if (!show.value) {
  132. pointer.value = null;
  133. return;
  134. } else if (pointer.value) {
  135. return;
  136. }
  137. clearTimeout(timeout);
  138. timeout = setTimeout(calcPointer, 16);
  139. };
  140. watch(show, resetPointer);
  141. watch(useViewerTransformConfig(), () => {
  142. pointer.value = null;
  143. resetPointer();
  144. });
  145. </script>
  146. <style lang="scss" scoped>
  147. .menu-item {
  148. display: block;
  149. min-width: 60px;
  150. margin-left: 10px;
  151. }
  152. </style>
  153. <style lang="scss">
  154. .propertys-controller {
  155. pointer-events: none;
  156. position: absolute;
  157. border-radius: 4px;
  158. border: 1px solid #e4e7ed;
  159. background-color: #ffffff;
  160. left: 0;
  161. top: 0;
  162. overflow: hidden;
  163. color: #303133;
  164. display: flex;
  165. flex-direction: column;
  166. align-items: center;
  167. box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
  168. min-width: 10px;
  169. padding: 5px 0;
  170. pointer-events: all;
  171. font-size: 14px;
  172. border: 1px solid var(--el-border-color-light);
  173. box-shadow: var(--el-dropdown-menu-box-shadow);
  174. background-color: var(--el-bg-color-overlay);
  175. border-radius: var(--el-border-radius-base);
  176. --el-menu-base-level-padding: 16px;
  177. --el-menu-item-height: 32px;
  178. --el-menu-item-font-size: 14px;
  179. .el-menu-item {
  180. align-items: center;
  181. padding: 5px 16px 5px 6px !important;
  182. color: var(--el-text-color-regular);
  183. }
  184. }
  185. .pointer-fade-enter-active,
  186. .pointer-fade-leave-active {
  187. transition: opacity 0.3s ease;
  188. .item {
  189. pointer-events: none;
  190. }
  191. }
  192. .pointer-fade-enter-from,
  193. .pointer-fade-leave-to {
  194. opacity: 0;
  195. }
  196. </style>