ComponentWrapper.vue 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. <template>
  2. <div
  3. class="component-wrapper"
  4. ref="componentWrapperRef"
  5. :style="warpperStyle"
  6. >
  7. <Container v-bind="componentData.container" @click="handleSelectComponent">
  8. <component
  9. :is="component"
  10. v-bind="componentData.props"
  11. :width="getComponentWidth"
  12. :height="getComponentHeight"
  13. />
  14. </Container>
  15. <div v-if="showEditBox" class="edit-box" :style="editWapperStyle">
  16. <span class="name-tip">{{ getTip }}</span>
  17. <UseDraggable
  18. v-for="item in dragPointList"
  19. :key="item"
  20. @move="(_, e) => handleDragPoint(item, e)"
  21. @start="handleDragStart"
  22. @end="handleDragEnd"
  23. >
  24. <span
  25. v-if="!componentData.locked"
  26. class="edit-box-point"
  27. :class="item"
  28. ></span>
  29. </UseDraggable>
  30. </div>
  31. </div>
  32. </template>
  33. <script setup lang="ts">
  34. import type { CustomElement } from "#/project";
  35. import { defineProps, defineAsyncComponent, computed, ref } from "vue";
  36. import { useStageStore } from "@/store/modules/stage";
  37. import { useProjectStore } from "@/store/modules/project";
  38. import { useDraggable } from "@vueuse/core";
  39. import { UseDraggable } from "@vueuse/components";
  40. import { asyncComponentAll } from 'shalu-dashboard-ui';
  41. import Container from "@/components/Container/index.vue";
  42. const { componentData } = defineProps<{ componentData: CustomElement }>();
  43. // 动态引入组件
  44. const component = defineAsyncComponent(
  45. asyncComponentAll[componentData.componentType]
  46. );
  47. const componentWrapperRef = ref<HTMLElement | null>(null);
  48. const stageStore = useStageStore();
  49. const projectStore = useProjectStore();
  50. const editWapperStyle = computed(() => {
  51. const { width = 400, height = 260 } = componentData.container.props || {};
  52. return {
  53. transform: `scale(${1 / stageStore.scale})`,
  54. transformOrigin: "50% 50%",
  55. width: `${width * stageStore.scale}px`,
  56. height: `${height * stageStore.scale}px`,
  57. border: "1px solid #1890ff",
  58. left: (width / 2) * (1 - stageStore.scale) + "px",
  59. top: (height / 2) * (1 - stageStore.scale) + "px",
  60. };
  61. });
  62. // 组件宽--根据边距计算
  63. const getComponentWidth = computed(() => {
  64. const { width = 400 } = componentData.container.props || {};
  65. const { paddingLeft = 0, paddingRight = 0 } = componentData.container.props || {};
  66. return width - paddingLeft - paddingRight;
  67. });
  68. // 组件高--根据边距计算
  69. const getComponentHeight = computed(() => {
  70. const { height = 260 } = componentData.container.props || {};
  71. const { paddingTop = 0, paddingBottom = 0 } = componentData.container.props || {};
  72. return height - paddingTop - paddingBottom;
  73. });
  74. const warpperStyle = computed(() => {
  75. const {
  76. width = 400,
  77. height = 260,
  78. x,
  79. y,
  80. } = componentData.container.props || {};
  81. // const style = transformStyle(componentData.container?.style || {});
  82. return {
  83. width: `${width}px`,
  84. height: `${height}px`,
  85. left: x + "px",
  86. top: y + "px",
  87. };
  88. });
  89. // 是否显示编辑框
  90. const showEditBox = computed(() => {
  91. return (
  92. projectStore.mode === "edit" &&
  93. projectStore.selectedElementKeys.includes(componentData.key)
  94. );
  95. });
  96. // 获取提示信息
  97. const getTip = computed(() => {
  98. const { x, y } = componentData.container.props || {};
  99. return showNameTip.value
  100. ? componentData.name
  101. : `x: ${Math.round(x)} y: ${Math.round(y)}`;
  102. });
  103. let isPointDragFlag = false;
  104. const showNameTip = ref(true);
  105. // 拖拽移动组件
  106. useDraggable(componentWrapperRef, {
  107. onMove: (position) => {
  108. if (isPointDragFlag) return;
  109. const originPosition = componentWrapperRef.value!.getBoundingClientRect();
  110. // 计算移动的距离
  111. const xMoveLength = position.x - originPosition.left;
  112. const yMoveLentgh = position.y - originPosition.top;
  113. const { x, y } = componentData.container.props || {};
  114. projectStore.updateElement(
  115. componentData.key,
  116. "container.props.x",
  117. Math.round(x + xMoveLength)
  118. );
  119. projectStore.updateElement(
  120. componentData.key,
  121. "container.props.y",
  122. Math.round(y + yMoveLentgh)
  123. );
  124. },
  125. onStart: () => {
  126. projectStore.setSelectedElementKeys([componentData.key]);
  127. showNameTip.value = false;
  128. },
  129. onEnd: () => {
  130. showNameTip.value = true;
  131. },
  132. });
  133. const handleSelectComponent = () => {
  134. projectStore.setSelectedElementKeys([componentData.key]);
  135. };
  136. /* ===============================缩放组件==================================== */
  137. const dragPointList = [
  138. "top-left",
  139. "top-center",
  140. "top-right",
  141. "left-center",
  142. "right-center",
  143. "bottom-left",
  144. "bottom-center",
  145. "bottom-right",
  146. ];
  147. const startPoint = {
  148. x: 0,
  149. y: 0,
  150. };
  151. // 拖拽点移动
  152. const handleDragPoint = (type: string, e: PointerEvent) => {
  153. const moveX = (e.x - startPoint.x) / stageStore.scale;
  154. const moveY = (e.y - startPoint.y) / stageStore.scale;
  155. let { x, y, width, height } = componentData.container.props || {};
  156. switch (type) {
  157. case "top-left":
  158. width -= moveX;
  159. height -= moveY;
  160. x += moveX;
  161. y += moveY;
  162. break;
  163. case "top-center":
  164. height -= moveY;
  165. y += moveY;
  166. break;
  167. case "top-right":
  168. width += moveX;
  169. height -= moveY;
  170. y += moveY;
  171. break;
  172. case "left-center":
  173. width -= moveX;
  174. x += moveX;
  175. break;
  176. case "right-center":
  177. width += moveX;
  178. break;
  179. case "bottom-left":
  180. width -= moveX;
  181. height += moveY;
  182. x += moveX;
  183. break;
  184. case "bottom-center":
  185. height += moveY;
  186. break;
  187. case "bottom-right":
  188. width += moveX;
  189. height += moveY;
  190. break;
  191. }
  192. startPoint.x = e.x;
  193. startPoint.y = e.y;
  194. if (width < 10 || height < 10) return;
  195. projectStore.updateElement(componentData.key, "container.props.x", Math.round(x));
  196. projectStore.updateElement(componentData.key, "container.props.y", Math.round(y));
  197. projectStore.updateElement(componentData.key, "container.props.width", Math.round(width));
  198. projectStore.updateElement(
  199. componentData.key,
  200. "container.props.height",
  201. Math.round(height)
  202. );
  203. };
  204. // 拖拽点开始
  205. const handleDragStart = (_: any, e: PointerEvent) => {
  206. startPoint.x = e.x;
  207. startPoint.y = e.y;
  208. isPointDragFlag = true;
  209. showNameTip.value = false;
  210. };
  211. // 拖拽点结束
  212. const handleDragEnd = () => {
  213. isPointDragFlag = false;
  214. showNameTip.value = true;
  215. };
  216. </script>
  217. <style lang="less" scoped>
  218. .component-wrapper {
  219. position: absolute;
  220. }
  221. .edit-box {
  222. position: absolute;
  223. &-point {
  224. position: absolute;
  225. width: 8px;
  226. height: 8px;
  227. background: #fff;
  228. border-radius: 50%;
  229. border: solid 1px @primary-color;
  230. }
  231. .name-tip {
  232. position: absolute;
  233. top: -20px;
  234. left: 4px;
  235. font-size: 12px;
  236. color: #fff;
  237. background: @primary-color;
  238. padding: 2px 4px;
  239. }
  240. .top-left {
  241. top: -4px;
  242. left: -4px;
  243. cursor: nw-resize;
  244. }
  245. .top-center {
  246. top: -4px;
  247. left: 50%;
  248. transform: translateX(-50%);
  249. transform-origin: center;
  250. cursor: n-resize;
  251. }
  252. .top-right {
  253. top: -4px;
  254. right: -4px;
  255. cursor: ne-resize;
  256. }
  257. .left-center {
  258. top: 50%;
  259. left: -4px;
  260. transform: translateY(-50%);
  261. cursor: w-resize;
  262. }
  263. .right-center {
  264. top: 50%;
  265. right: -4px;
  266. transform: translateY(-50%);
  267. cursor: e-resize;
  268. }
  269. .bottom-left {
  270. bottom: -4px;
  271. left: -4px;
  272. cursor: sw-resize;
  273. }
  274. .bottom-center {
  275. bottom: -4px;
  276. left: 50%;
  277. transform: translateX(-50%);
  278. cursor: s-resize;
  279. }
  280. .bottom-right {
  281. bottom: -4px;
  282. right: -4px;
  283. cursor: se-resize;
  284. }
  285. }
  286. </style>