Эх сурвалжийг харах

Merge branch 'dev' of http://124.70.149.18:10880/lianghj/queenshow into dev

lianghongjie 1 жил өмнө
parent
commit
64cd2746b6
37 өөрчлөгдсөн 1400 нэмэгдсэн , 84 устгасан
  1. 3 0
      src/assets/icons/components/IconCroperr.tsx
  2. 3 0
      src/assets/icons/components/IconCross.tsx
  3. 3 0
      src/assets/icons/components/IconRight.tsx
  4. 3 0
      src/assets/icons/index.ts
  5. 1 0
      src/assets/icons/svg/croperr.svg
  6. 1 0
      src/assets/icons/svg/cross.svg
  7. 1 0
      src/assets/icons/svg/right.svg
  8. 41 14
      src/modules/editor/components/CompUI/basicUI/Image2/component.tsx
  9. 2 1
      src/modules/editor/components/CompUI/basicUI/Image2/index.ts
  10. 30 4
      src/modules/editor/components/CompUI/basicUI/Page/PageMusic.tsx
  11. 17 3
      src/modules/editor/components/CompUI/basicUI/Text/component.tsx
  12. 325 0
      src/modules/editor/components/CompUI/basicUI/Transfer/transform.tsx
  13. 15 0
      src/modules/editor/components/TipIcons/index.ts
  14. 8 10
      src/modules/editor/components/Viewport/Content/index.tsx
  15. 164 13
      src/modules/editor/components/Viewport/Toolbar/AiText.tsx
  16. 24 9
      src/modules/editor/components/Viewport/Toolbar/index.tsx
  17. 220 0
      src/modules/editor/controllers/CropperCtrl/index.ts
  18. 4 0
      src/modules/editor/controllers/HistoryCtrl/index.ts
  19. 4 0
      src/modules/editor/controllers/HotKeyCtrl/index.ts
  20. 3 0
      src/modules/editor/controllers/MediaCtrl/indext.ts
  21. 3 2
      src/modules/editor/controllers/SelectCtrl/ObjsContainer.ts
  22. 5 3
      src/modules/editor/controllers/SelectCtrl/assistMagnetCtrl.ts
  23. 5 1
      src/modules/editor/controllers/SelectCtrl/index.ts
  24. 240 0
      src/modules/editor/controllers/TransformCtrl/index.ts
  25. 79 0
      src/modules/editor/controllers/TransformCtrl/mouseDown.ts
  26. 19 0
      src/modules/editor/controllers/TransformCtrl/mouseMove.ts
  27. 66 0
      src/modules/editor/controllers/TransformCtrl/mouseUp.ts
  28. 52 0
      src/modules/editor/controllers/TransformCtrl/state.ts
  29. 4 0
      src/modules/editor/module/actions/edit.ts
  30. 8 0
      src/modules/editor/module/actions/editWithManualHistory.ts
  31. 26 24
      src/modules/editor/module/actions/image.tsx
  32. 2 0
      src/modules/editor/module/index.ts
  33. 1 0
      src/modules/editor/module/stores/index.ts
  34. 2 0
      src/modules/editor/objects/Toolbars/CompToolbars.ts
  35. 10 0
      src/modules/editor/objects/Toolbars/default.ts
  36. 1 0
      src/pages/website/Promotion2/controller.tsx
  37. 5 0
      yarn.lock

+ 3 - 0
src/assets/icons/components/IconCroperr.tsx

@@ -0,0 +1,3 @@
+
+import { createIcon } from '@queenjs/icons';
+export const IconCroperr = createIcon(<svg viewBox="0 0 15.143 14.943"><g transform="translate(-828.872 -93.985)"><g transform="translate(824.5 89.62)"><rect fill="none" stroke="currentColor" stroke-linejoin="round" width="7.631" height="7.631" rx="1" transform="translate(5.072 11.177)"/><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-linecap="round" d="M30,6h4.579v4.579" transform="translate(-15.771 -0.928)"/><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-linecap="round" d="M6,30.79l2.335-1.751a1.145,1.145,0,0,1,1.412.03l3.5,2.866" transform="translate(-0.928 -15.035)"/><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-linecap="round" d="M34.579,6,30,10.579" transform="translate(-15.771 -0.928)"/></g><g transform="translate(823.572 88.692)"><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-linecap="round" d="M7.483,6H6V7.507"/><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-linecap="round" d="M16.028,6h1.921" transform="translate(-6.283)"/><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-linecap="round" d="M6,16v.664" transform="translate(0 -6.265)"/></g><g transform="translate(828.344 93.463)"><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-linecap="round" d="M6,27.5H7.493V26.012" transform="translate(7.471 -12.537)"/><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-linecap="round" d="M30,16v1.444" transform="translate(-15.035 -6.695)"/><path fill="none" stroke="currentColor" stroke-linecap="round" d="M15.992,30h.66" transform="translate(-5.266 -15.035)"/></g></g></svg>)

+ 3 - 0
src/assets/icons/components/IconCross.tsx

@@ -0,0 +1,3 @@
+
+import { createIcon } from '@queenjs/icons';
+export const IconCross = createIcon(<svg viewBox="0 0 9.878 9.878"><g transform="translate(-13.151 -13.151)"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2px" d="M14,14l8.181,8.181"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2px" d="M14,22.181,22.181,14"/></g></svg>)

+ 3 - 0
src/assets/icons/components/IconRight.tsx

@@ -0,0 +1,3 @@
+
+import { createIcon } from '@queenjs/icons';
+export const IconRight = createIcon(<svg viewBox="0 0 13.947 9.83"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2px" d="M17.25,11,8.828,19.382,5,15.572" transform="translate(-4.151 -10.151)"/></svg>)

+ 3 - 0
src/assets/icons/index.ts

@@ -6,6 +6,8 @@ export * from "./components/IconAlignC";
 export * from "./components/IconAlignL";
 export * from "./components/IconAlignR";
 export * from "./components/IconClear";
+export * from "./components/IconCroperr";
+export * from "./components/IconCross";
 export * from "./components/IconCube";
 export * from "./components/IconFloatOff";
 export * from "./components/IconFloatOn";
@@ -18,6 +20,7 @@ export * from "./components/IconMove";
 export * from "./components/IconMusic";
 export * from "./components/IconQueen";
 export * from "./components/IconResizeY";
+export * from "./components/IconRight";
 export * from "./components/IconRotate";
 export * from "./components/IconText";
 export * from "./components/IconVideo";

+ 1 - 0
src/assets/icons/svg/croperr.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 15.143 14.943"><g transform="translate(-828.872 -93.985)"><g transform="translate(824.5 89.62)"><rect fill="none" stroke="currentColor" stroke-linejoin="round" width="7.631" height="7.631" rx="1" transform="translate(5.072 11.177)"/><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-linecap="round" d="M30,6h4.579v4.579" transform="translate(-15.771 -0.928)"/><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-linecap="round" d="M6,30.79l2.335-1.751a1.145,1.145,0,0,1,1.412.03l3.5,2.866" transform="translate(-0.928 -15.035)"/><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-linecap="round" d="M34.579,6,30,10.579" transform="translate(-15.771 -0.928)"/></g><g transform="translate(823.572 88.692)"><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-linecap="round" d="M7.483,6H6V7.507"/><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-linecap="round" d="M16.028,6h1.921" transform="translate(-6.283)"/><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-linecap="round" d="M6,16v.664" transform="translate(0 -6.265)"/></g><g transform="translate(828.344 93.463)"><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-linecap="round" d="M6,27.5H7.493V26.012" transform="translate(7.471 -12.537)"/><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-linecap="round" d="M30,16v1.444" transform="translate(-15.035 -6.695)"/><path fill="none" stroke="currentColor" stroke-linecap="round" d="M15.992,30h.66" transform="translate(-5.266 -15.035)"/></g></g></svg>

+ 1 - 0
src/assets/icons/svg/cross.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 9.878 9.878"><g transform="translate(-13.151 -13.151)"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2px" d="M14,14l8.181,8.181"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2px" d="M14,22.181,22.181,14"/></g></svg>

+ 1 - 0
src/assets/icons/svg/right.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 13.947 9.83"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2px" d="M17.25,11,8.828,19.382,5,15.572" transform="translate(-4.151 -10.151)"/></svg>

+ 41 - 14
src/modules/editor/components/CompUI/basicUI/Image2/component.tsx

@@ -4,6 +4,8 @@ import { defineComponent, watch } from "vue";
 import { string } from "vue-types";
 import { useCompData } from ".";
 import { View } from "../View";
+import { css } from "@linaria/core";
+
 
 export const Component = defineComponent({
   props: {
@@ -29,6 +31,9 @@ export const Component = defineComponent({
       };
     }
 
+
+
+
     async function changeVal() {
       try {
         const url = await controls.pickCtrl.pickOneImage();
@@ -38,6 +43,7 @@ export const Component = defineComponent({
         comp.value.x = 0;
         comp.value.y = 0;
         comp.value.s = 1;
+        comp.value.matrix = "";
       } catch (error) {
         console.log(error);
       }
@@ -60,10 +66,28 @@ export const Component = defineComponent({
         scale + "" == "1" && ox + "" == "0" && oy + "" == "0"
           ? "cover"
           : "contain";
+      const isCropping = controls.cropCtrl.state.compId == props.compId;
+
+      const offsetUnit = value.offsetUnit || "%";
+
+      const styleObj = {
+        transform: `scale(${scale}) translate(${ox}${offsetUnit},${oy}${offsetUnit})`,
+        objectFit,
+        transformOrigin: "center",
+        width: "100%",
+        height: "100%"
+      }
+
+      if (value.matrix) {
+        styleObj.transform = value.matrix;
+        styleObj.transformOrigin = "0 0";
+        styleObj.width = value.w + "px";
+        styleObj.height = value.h + "px";
+      }
 
       return (
         <View
-          class="overflow-hidden"
+          class={ [ "overflow-hidden"]}
           compId={props.compId}
           onDblclick={store.isEditMode ? changeVal : undefined}
           onClick={() => {
@@ -74,21 +98,24 @@ export const Component = defineComponent({
             }
           }}
         >
-          <img
-            crossorigin="anonymous"
-            class={"w-1/1 h-1/1 object-cover pointer-events-none"}
-            style={{
-              transform: `scale(${scale}) translate(${ox}%,${oy}%)`,
-              objectFit,
-            }}
-            src={
-              value.url.startsWith("data:image/png")
-                ? value.url
-                : value.url + "?editMode=" + store.isEditMode
-            }
-          />
+          <div   class={["w-1/1 h-1/1 object-cover pointer-events-none", isCropping && cropingBorder]} >
+            <img
+                  crossorigin="anonymous"
+                  class={"w-1/1 h-1/1 object-cover"}
+                  style={styleObj as any}
+                  src={
+                    value.url.startsWith("data:image/png")
+                      ? value.url
+                      : value.url + "?editMode=" + store.isEditMode
+                  }
+              />
+          </div>
         </View>
       );
     };
   },
 });
+
+const cropingBorder = css`
+ border: 1px solid orange;
+`

+ 2 - 1
src/modules/editor/components/CompUI/basicUI/Image2/index.ts

@@ -14,8 +14,9 @@ export const { createComp, useCompData } = createCompHooks({
     url: Dict_Imgs.Default,
     x: 0,
     y: 0,
-    s: 1,
+    s: 1.875,
     showLink: false,
+    offsetUnit: "%",
     link: "",
   },
   layout: {

+ 30 - 4
src/modules/editor/components/CompUI/basicUI/Page/PageMusic.tsx

@@ -6,7 +6,14 @@ import { css } from "@linaria/core";
 import { Button, Slider } from "ant-design-vue";
 import { Howl } from "howler";
 import { nanoid } from "nanoid";
-import { defineComponent, reactive, ref, watch } from "vue";
+import {
+  defineComponent,
+  reactive,
+  ref,
+  watch,
+  onUnmounted,
+  onMounted,
+} from "vue";
 import { bool, number } from "vue-types";
 declare const WeixinJSBridge: any;
 export const PageMusic = defineComponent({
@@ -21,7 +28,6 @@ export const PageMusic = defineComponent({
     const rootComp = helper.findRootComp();
     let audioKey = nanoid();
     let audioBgm = ref();
-
     const initAudioBgm = () => {
       audioBgm.value = null;
       audioBgm.value = new Howl({
@@ -30,9 +36,9 @@ export const PageMusic = defineComponent({
         preload: true,
         HTML5: true,
       });
+      controls.mediaCtrl.setMediasInstance(audioKey, audioBgm.value);
       audioBgm.value.on("load", () => {
         state.duration = audioBgm.value.duration();
-        controls.mediaCtrl.setMediasInstance(audioKey, audioBgm.value);
         if (!store.isEditMode) {
           if (isWeixinBrowser()) {
             WeixinJSBridge.invoke(
@@ -69,8 +75,19 @@ export const PageMusic = defineComponent({
           audioRest();
         }
       });
+      setTimeout(() => {
+        checkAutoPlay();
+      }, 500);
+    };
+    const checkAutoPlay = () => {
+      if (!audioBgm.value || store.isEditMode) {
+        return;
+      }
+      let playing = audioBgm.value.playing();
+      if (!playing) {
+        playAudio(true);
+      }
     };
-    initAudioBgm();
     const playStep = () => {
       if (!audioBgm.value) {
         return;
@@ -94,6 +111,7 @@ export const PageMusic = defineComponent({
         audioRest();
       }
       let playing = audioBgm.value.playing();
+      console.log(playing);
       if (status && playing) {
         state.playStatus = true;
         return;
@@ -107,6 +125,14 @@ export const PageMusic = defineComponent({
         initAudioBgm();
       }
     );
+    onMounted(() => {
+      initAudioBgm();
+    });
+    onUnmounted(() => {
+      audioRest();
+      audioBgm.value = null;
+      controls.mediaCtrl.removeMedia(audioKey);
+    });
     const seekChange = (v: number) => {
       state.currentTime = v;
       audioBgm.value && audioBgm.value.seek(v);

+ 17 - 3
src/modules/editor/components/CompUI/basicUI/Text/component.tsx

@@ -150,11 +150,27 @@ const EditorComp = defineComponent({
     let blurCanceler: any = null;
     onMounted(() => {
       blurCanceler = blurHandle();
+      nextTick(() => {
+        initHeight();
+      });
     });
     onUnmounted(() => {
       blurCanceler?.();
     });
-
+    const preHeight = ref<number>(0);
+    const initHeight = () => {
+      const h = helper.pxToDesignSize(inputRef.value?.$el.clientHeight);
+      const isChange = Math.abs(preHeight.value - h) > 1;
+      preHeight.value = h;
+      actions.updateCompData(comp, "layout.size.1", preHeight.value);
+      helper.extendStreamCard(store.currStreamCardId);
+      if (isChange) {
+        actions.selectObjs([]);
+        setTimeout(() => {
+          actions.selectObjs([props.compId]);
+        }, 0);
+      }
+    };
     function isInCkBodyWrapper(dom: HTMLElement) {
       if (editorInstance.value) {
         const in1 =
@@ -190,8 +206,6 @@ const EditorComp = defineComponent({
       };
     }
 
-    const preHeight = ref<number>(0);
-
     return () => (
       <ckeditor
         class={textStyle}

+ 325 - 0
src/modules/editor/components/CompUI/basicUI/Transfer/transform.tsx

@@ -0,0 +1,325 @@
+import { IconRotate , IconMove} from "@/assets/icons";
+import { css } from "@linaria/core";
+import { defineComponent, onMounted, ref, nextTick, reactive } from "vue";
+import { any } from "vue-types";
+import { TransformCtrl } from "@/modules/editor/controllers/TransformCtrl";
+
+export const Transforms = defineComponent({
+    props: {
+        ctrl: any<TransformCtrl>()
+    },
+
+  setup(props) {
+
+    const ctrl = props.ctrl ? props.ctrl : new TransformCtrl();
+    const toolbarRef = ref<HTMLElement>();
+    const transformRef = ref<HTMLElement>();
+  
+    const state = reactive({
+       toolbarW: 0,
+    })
+    onMounted(()=>{
+        if (transformRef.value) ctrl.initUI(transformRef.value);
+
+        nextTick(()=>{
+            nextTick(()=>{
+              if (!toolbarRef.value) {
+                return
+              }  
+              const r = toolbarRef.value.getBoundingClientRect();
+              state.toolbarW = r.width;
+            })
+        })
+    })
+    
+    return () => {
+
+      let toolbarTop = ctrl.state.bbox.y
+      if (toolbarTop < 0 ) {
+        toolbarTop = 0;
+      }
+      
+      return (
+        <div
+          ref={transformRef}
+          class={[
+            "absolute transfer z-998",
+            ctrl.state.visible ? showgizmo : hideGizmo,
+          ]}
+          style={{
+            top: ctrl.state.baseTop + "px"
+          }}
+        >
+          <div
+            id= "toolbar"
+            class={toolbarStyle}
+            style={{
+              top: toolbarTop + "px",
+              left: (ctrl.state.bbox.x + ctrl.state.bbox.w / 2.0 - state.toolbarW / 2.0)  + "px",
+            }}
+            ref= {toolbarRef}
+          >
+            {
+              ctrl.state.toolbarNames.map((name) => {
+                const ToolBarComp = ctrl.toolbars[name];
+                return <span key={name} data-toolname={name} ><ToolBarComp class="p-4px" /></span> 
+              })
+            }
+          </div>
+
+          <div
+            class={["absolute", selctRectStyle]}
+            id="movecenter"
+            style={{
+              width: ctrl.state.width + "px",
+              height: ctrl.state.height + "px",
+              transform: ctrl.state.matrix,
+              transformOrigin: `0 0`,
+            }}
+          >
+            <div class={borderStyle} style={{ transform: ctrl.state.matrixInvert, transformOrigin: `0 0`}} >
+                <div class={borderContentStyle} style={{width: ctrl.state.relWidth + "px", height: ctrl.state.relHeight + "px"}}></div>
+            </div>
+            <>
+              {
+                <div
+                  class={[resizeStyle, scaleBottomRightStyle]}
+                  style={{ transform: ctrl.state.matrixInvert }}
+                  id="scaleBottomright"
+                />
+              }
+
+                {
+                    <div
+                    class={[resizeStyle, scaleBottomLeftStyle]}
+                    style={{ transform: ctrl.state.matrixInvert }}
+                    id="scaleBottomleft"
+                  />
+                }
+
+              {
+                  <div
+                  class={[resizeStyle, scaleTopLeftStyle]}
+                  style={{ transform: ctrl.state.matrixInvert }}
+                  id="scaleTopleft"
+                />
+              }
+              {
+                <div
+                  class={[resizeStyle, scaleTopRightStyle]}
+                  style={{ transform: ctrl.state.matrixInvert }}
+                  id="scaleTopright"
+                />
+              }
+
+              <div class={transformBtnsStyle} style={{ transform: ctrl.state.matrixInvert }}>
+
+                <div
+                    class={transBtnStyle}
+                    id="moveicon"
+                  >
+                    <IconMove />
+                </div>
+
+                <div
+                  class={transBtnStyle}
+                  id = "rotate"
+                >
+                  <IconRotate  />
+                </div>
+
+              </div>
+              {ctrl.state.showScaleBridge && ctrl.state.showScaletop && (
+                <div
+                  class={[resizeHeightBtnCls, scaleTopCls]}
+                  style={{ transform: ctrl.state.matrixInvert }}
+                  id="scaletop"
+                />
+              )}
+              {ctrl.state.showScaleBridge && ctrl.state.showScalebottom && (
+                <div
+                  class={[resizeHeightBtnCls, scaleBottomCls]}
+                  style={{ transform: ctrl.state.matrixInvert }}
+                  id="scalebottom"
+                />
+              )}
+              {ctrl.state.showScaleBridge &&  (
+                <div
+                  class={[resizeWidthBtnCls, scaleRightCls]}
+                  style={{ transform: ctrl.state.matrixInvert }}
+                  id="scaleright"
+                />
+              )}
+              {ctrl.state.showScaleBridge && (
+                <div
+                  class={[resizeWidthBtnCls, scaleLeftCls]}
+                  style={{ transform: ctrl.state.matrixInvert }}
+                  id="scaleleft"
+                />
+              )}
+            </>
+          </div>
+        </div>
+      );
+    };
+  },
+});
+const selctRectStyle = css`
+  /* pointer-events: none; */
+`;
+const showgizmo = css`
+  display: block;
+  left: 0;
+  top: 0;
+  /* pointer-events: none; */
+`;
+const hideGizmo = css`
+  display: none;
+`;
+
+const borderStyle = css`
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  pointer-events: none;
+  z-index: 999;
+`;
+const borderContentStyle = css`
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  outline: 2px solid @inf-primary-color;
+`
+
+const resizeStyle = css`
+  position: absolute;
+  width: 16px;
+  height: 16px;
+  border-radius: 50%;
+  background-color: #fff;
+  z-index: 999;
+  transform: translate(50%, 50%);
+  pointer-events: auto;
+  box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.2);
+  cursor: nwse-resize;
+  &:hover {
+    border-color: @inf-primary-color;
+  }
+`;
+
+const scaleBottomRightStyle = css`
+  bottom: -8px;
+  right: -8px;
+`;
+const scaleBottomLeftStyle = css`
+  bottom: -8px;
+  left: -8px;
+`;
+
+const scaleTopLeftStyle = css`
+  top: -8px;
+  left: -8px;
+`;
+
+const scaleTopRightStyle = css`
+  top: -8px;
+  right: -8px;
+`;
+
+const transformBtnsStyle = css`
+  @apply space-x-10px whitespace-nowrap;
+  position: absolute;
+  bottom: 0px;
+  left:calc(50% - 32px);
+  font-size: 16px;
+  z-index: 999;
+  pointer-events: auto;
+  transform-origin: 50% 100%;
+  pointer-events: none;
+`;
+
+const transBtnStyle = css`
+  display: inline-block;
+  width: 28px;
+  height: 28px;
+  border-radius: 50%;
+  background-color: #fff;
+  text-align: center;
+  line-height: 28px;
+  font-size: 16px;
+  color: #333;
+  position: relative;
+  top: 50px;
+  @apply shadow cursor-move;
+  pointer-events: auto;
+
+  &:hover {
+    color: #fff;
+    background-color: @inf-primary-color;
+  }
+`;
+
+const toolbarStyle = css`
+  @apply bg-white shadow rounded space-x-4px p-4px whitespace-nowrap;
+  position: absolute;
+  top: 0;
+  left: 50%;
+  transform: translate(0%, -60px);
+  z-index: 999;
+`;
+
+const resizeHeightBtnCls = css`
+  position: absolute;
+  width: 30px;
+  height: 8px;
+  border-radius: 4px;
+  left: 50%;
+  transform: translate(-50%, -4px);
+  pointer-events: auto;
+  cursor: ns-resize;
+  background: rgba(255, 255, 255, 0.3);
+  &:hover {
+    background: @inf-primary-color;
+  }
+  @apply shadow;
+  z-index: 999;
+`;
+
+const scaleTopCls = css`
+  top: -4px;
+  left: calc(50% - 15px);
+`;
+
+const scaleBottomCls = css`
+  bottom:-4px;
+  left: calc(50% - 15px);
+`;
+
+const resizeWidthBtnCls = css`
+  position: absolute;
+  width: 8px;
+  height: 30px;
+  border-radius: 4px;
+  pointer-events: auto;
+  cursor: ew-resize;
+  background: rgba(255, 255, 255, 0.3);
+  &:hover {
+    background: @inf-primary-color;
+  }
+  @apply shadow;
+  z-index: 999;
+`;
+
+const scaleRightCls = css`
+  right: -4px;
+  top: calc(50% - 15px);
+`;
+
+const scaleLeftCls = css`
+  left: -4px;
+  top: calc(50% - 15px);
+`;

+ 15 - 0
src/modules/editor/components/TipIcons/index.ts

@@ -3,11 +3,14 @@ import {
   IconAlignL,
   IconAlignR,
   IconClear,
+  IconCroperr,
+  IconCross,
   IconFloatOff,
   IconFloatOn,
   IconLayerDown,
   IconLayerUp,
   IconQueen,
+  IconRight,
   IconAi,
 } from "@/assets/icons";
 import {
@@ -94,4 +97,16 @@ export const TipIcons = {
     icons: [IconClose],
     tips: ["取消打组"],
   }),
+  Cross: createTipIcon({
+    icons: [IconCross],
+    tips: ["取消"],
+  }),
+  Right: createTipIcon({
+    icons: [IconRight],
+    tips: ["确定"],
+  }),
+  Cropper: createTipIcon({
+    icons: [IconCroperr],
+    tips: ["裁剪"],
+  }),
 };

+ 8 - 10
src/modules/editor/components/Viewport/Content/index.tsx

@@ -7,8 +7,10 @@ import { useEditor } from "../../..";
 import { HotKeyCtrl } from "../../../controllers/HotKeyCtrl";
 import { CompUI } from "../../CompUI";
 import { Transfer } from "../../CompUI/basicUI/Transfer";
-import { StreamCardTransfer } from "../../CompUI/basicUI/Transfer/streamCard";
+import { Transforms } from "../../CompUI/basicUI/Transfer/transform";
+
 import { SelectTransfer } from "../../CompUI/basicUI/Transfer/select";
+import { TipIcons } from "../../TipIcons";
 
 export default defineUI({
   setup() {
@@ -30,6 +32,7 @@ export default defineUI({
     const containRef = ref();
     const selectCanvasRef = ref();
     const viewportRef = ref();
+    controls.cropCtrl.modifyCtrl.toolbars = TipIcons;
 
     return () => {
       const pageRoot = helper.findRootComp();
@@ -102,15 +105,10 @@ export default defineUI({
                           />
                         )} */}
 
-                        {/* {store.currCompId &&
-                          store.currStreamCardId &&
-                          store.currCompId !== "root" &&
-                          !store.textEditingState &&
-                          store.currCompId !== store.currStreamCardId &&
-                          !state.draging && (
-                            <Transfer key={store.currCompId + streamCardIndex} />
-                          )} */}
-
+                        { 
+                          !state.draging && controls.cropCtrl.state.visible && <Transforms ctrl={ controls.cropCtrl.modifyCtrl} />
+                        }
+                        
                         {!state.draging && <SelectTransfer />}
                       </>
                     );

+ 164 - 13
src/modules/editor/components/Viewport/Toolbar/AiText.tsx

@@ -1,28 +1,179 @@
 import { useEditor } from "@/modules/editor";
-import { defineComponent } from "vue";
-import { Button, Input } from "ant-design-vue";
+import { CloseOutlined } from "@ant-design/icons-vue";
+import { defineComponent, reactive } from "vue";
+import { Button, Input, message } from "ant-design-vue";
+import { css } from "@linaria/core";
+import { queenApi } from "queenjs";
 export default defineComponent({
-  setup() {
-    const { store } = useEditor();
+  emits: ["visible"],
+  setup(props, { emit }) {
+    const { store, actions } = useEditor();
+    const state = reactive({
+      loading: false,
+      inputValue: "",
+      aiValue: "",
+      boxFocus: false,
+      generated: false,
+    });
+    const generateWord = () => {
+      if (state.loading) {
+        return;
+      }
+      state.loading = true;
+      const reqData = {
+        model: "gpt-3.5-turbo",
+        messages: [
+          {
+            role: "user",
+            content: state.inputValue,
+          },
+        ],
+      };
+      const xhr = new XMLHttpRequest();
+      xhr.open(
+        "post",
+        "http://186b2d5554134321a0afd4b1be443273.apig.ap-southeast-1.huaweicloudapis.com/chatgpt"
+      );
+      xhr.setRequestHeader("content-type", "application/json");
+      xhr.send(JSON.stringify(reqData));
+      xhr.onload = function (e: any) {
+        let result = e.target?.responseText;
+
+        try {
+          result = JSON.parse(result);
+          const { choices } = result;
+          const message = choices[0].message.content;
+          state.aiValue = message;
+          state.generated = true;
+        } catch (e) {
+          queenApi.messageError("生成失败!");
+          console.log(e);
+        } finally {
+          state.loading = false;
+        }
+      };
+      xhr.onerror = (e: any) => {
+        queenApi.messageError("生成失败!");
+        console.log(e);
+        state.loading = false;
+      };
+    };
+    const addText = async () => {
+      const aitext = `<p style="line-height:1.5;"><span style="font-size:14px;">${state.aiValue}</span></p>`;
+      await actions.clickCompToDesign("Text", (comp) => {
+        actions.updateCompData(comp, "value", aitext);
+      });
+    };
 
     return () => (
-      <div class="w-280px p-20px bg-component border-r-4px">
+      <div class={[AIStyle, "w-280px p-20px bg-component"]}>
         <div>
-          <div>AI提示词</div>
-          <div>
-            <Input.TextArea showCount={true} />
+          <div class={"flex justify-between items-center "}>
+            AI提示词
+            <CloseOutlined
+              onClick={() => {
+                emit("visible", false);
+              }}
+            />
+          </div>
+          <div class={"mt-14px"}>
+            <div class={["input_box pb-12px", state.boxFocus ? "focus" : ""]}>
+              <Input.TextArea
+                class={"ai_input  scrollbar"}
+                value={state.inputValue}
+                maxlength={1000}
+                onChange={(e) => {
+                  if (state.inputValue.length >= 1000) {
+                    return;
+                  }
+                  state.inputValue = e.target.value || "";
+                }}
+                onFocus={() => {
+                  state.boxFocus = true;
+                }}
+                onBlur={() => {
+                  state.boxFocus = false;
+                }}
+                autoSize={{ minRows: 3, maxRows: 6 }}
+                placeholder={"请输入关键词,AI将自动帮您生成对应文案"}
+              />
+              <div class={"px-12px text-right f-12px text-gray"}>
+                {state.inputValue.length}/1000
+              </div>
+            </div>
           </div>
         </div>
-        <div>
+        <div class={"mt-24px"}>
           <div>结果生成</div>
-          <div>
-            <Input.TextArea />
+          <div class={"mt-14px result_text"}>
+            <Input.TextArea
+              class={"ai_input  scrollbar"}
+              autoSize={{ minRows: 6, maxRows: 6 }}
+              readonly
+              value={state.aiValue}
+            />
           </div>
         </div>
-        <div>
-          <Button type="primary">开始生成</Button>
+        <div class={"mt-24px space-y-10px"}>
+          {state.generated ? (
+            <Button
+              block
+              class={"fade_input"}
+              type="primary"
+              onClick={generateWord}
+              loading={state.loading}
+            >
+              重新生成
+            </Button>
+          ) : (
+            <Button
+              block
+              type="primary"
+              onClick={generateWord}
+              loading={state.loading}
+            >
+              开始生成
+            </Button>
+          )}
+          {state.generated && (
+            <Button block type="primary" onClick={addText}>
+              添加至图层
+            </Button>
+          )}
         </div>
       </div>
     );
   },
 });
+const AIStyle = css`
+  border-radius: 4px;
+  box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.2);
+  .input_box,
+  .result_text {
+    position: relative;
+    background-color: #303030;
+    border-radius: 4px;
+
+    .ai_input {
+      font-size: 12px;
+      border-color: transparent;
+      &:hover,
+      &:focus {
+        border-color: transparent;
+        box-shadow: none;
+      }
+    }
+  }
+  .input_box {
+    border: 1px solid transparent;
+    &.focus,
+    &:hover {
+      border-color: @inf-primary-color;
+    }
+  }
+  .fade_input {
+    color: @inf-primary-color;
+    border-color: @inf-primary-fade-color;
+    background-color: @inf-primary-fade-color;
+  }
+`;

+ 24 - 9
src/modules/editor/components/Viewport/Toolbar/index.tsx

@@ -5,34 +5,49 @@ import { Dropdown } from "ant-design-vue";
 import { css } from "@linaria/core";
 import { useLauncher } from "@/modules/launcher";
 import AiText from "./AiText";
+import { reactive } from "vue";
 export default defineUI({
   setup() {
     const { actions, controls } = useEditor();
     const launcher = useLauncher();
     const { history } = controls.historyCtrl;
-
+    const state = reactive({
+      aiVisible: false,
+    });
     return () => (
       <>
         <div class="absolute top-20px left-20px space-x-10px z-999">
           <TipIcons.Undo
-            disable={!history.state.canUndo}
+            disable={ !controls.historyCtrl.state.enable || !history.state.canUndo}
             class={btnCls}
             onClick={() => history.undo()}
           />
           <TipIcons.Redo
-            disable={!history.state.canRedo}
+            disable={ !controls.historyCtrl.state.enable || !history.state.canRedo}
             class={btnCls}
             onClick={() => history.redo()}
           />
         </div>
         <div class="absolute top-20px right-20px space-x-10px z-999">
-          {/* <Dropdown
-            overlay={<AiText />}
-            placement="bottomRight"
-            trigger="click"
+          <Dropdown
+            overlay={
+              <AiText
+                onVisible={(v) => {
+                  state.aiVisible = v;
+                }}
+              />
+            }
+            destroyPopupOnHide={true}
+            placement="bottom"
+            visible={state.aiVisible}
           >
-            <TipIcons.AiText class={btnCls} />
-          </Dropdown> */}
+            <TipIcons.AiText
+              class={btnCls}
+              onClick={() => {
+                state.aiVisible = !state.aiVisible;
+              }}
+            />
+          </Dropdown>
           <TipIcons.Screenshot
             class={btnCls}
             onClick={() => actions.updateThumbnailByScreenshot(true)}

+ 220 - 0
src/modules/editor/controllers/CropperCtrl/index.ts

@@ -0,0 +1,220 @@
+import { ModuleControl } from "queenjs";
+import { reactive } from "vue";
+import { EditorModule } from "../../module";
+import { CompObject } from "../SelectCtrl/compObj";
+import { ObjsContainer } from "../SelectCtrl/ObjsContainer";
+import { Matrix } from "../SelectCtrl/matrix";
+import { TransformCtrl } from "../TransformCtrl";
+
+function createImage(url:string) :Promise<HTMLImageElement> {
+    return new Promise((r)=>{
+        const img= new Image();
+        img.onload = ()=>{
+            r(img);
+        }
+        img.src = url;
+    })
+}
+
+export class ImageCropperCtrl extends ModuleControl<EditorModule> {
+    state = reactive({
+        compId: "",
+        visible: false,
+    });
+
+    modifyCtrl = new TransformCtrl();
+
+    currSelected :string[]= [];
+    initValue = {
+        w: 0,
+        h: 0,
+        matrix: ""
+    }
+    lastValue = {
+        w: 0,
+        h: 0,
+        matrix: ""
+    }
+
+    constructor(moduel:EditorModule) {
+        super(moduel);
+
+        this.modifyCtrl.on("onClickToolbar", (name:"Right" | "Cross")=>{
+            this.modifyCtrl.state.visible = false;
+            this.controls.historyCtrl.state.enable = true;
+            this.store.selected = this.currSelected
+            const initW = this.initValue.w;
+            const initH = this.initValue.h;
+            const initMtrx = this.initValue.matrix;
+
+            if (name == "Cross") { //取消操作
+                const compId = this.state.compId
+                const imgComp = this.store.compMap[compId];
+                imgComp.value.matrix = initMtrx;
+                imgComp.value.w = initW;
+                imgComp.value.h = initH;
+                this.state.compId = "";
+                return;
+            }
+            //确定操作
+            const history = this.controls.historyCtrl.history;
+            const compId = this.state.compId
+            this.state.compId = "";
+            const lastW  = this.lastValue.w;
+            const lastH = this.lastValue.h;
+            const lastMatrix = this.lastValue.matrix;
+            history.record({
+                undo: ()=> {
+                    const imgComp = this.store.compMap[compId];
+                    imgComp.value.matrix = initMtrx;
+                    imgComp.value.w = initW;
+                    imgComp.value.h = initH;
+                },
+                redo: ()=> {
+                    const imgComp = this.store.compMap[compId];
+                    imgComp.value.matrix = lastMatrix;
+                    imgComp.value.w = lastW;
+                    imgComp.value.h = lastH;
+                }
+            } as any)
+        })
+
+        let tmpM = new Matrix();
+        let tmpM2 = new Matrix();
+
+        this.modifyCtrl.on("change", (dtx:number, dty:number)=>{
+            
+            const imgComp = this.store.compMap[this.state.compId];
+
+            tmpM.copyFrom(this.modifyCtrl.objContainer.parent.worldTransform);
+
+            const comp = new CompObject(imgComp);
+
+            tmpM2.copyFrom(comp.worldTransform)
+            tmpM2.invert()
+            tmpM.prepend(tmpM2)
+
+            this.lastValue.w = this.modifyCtrl.objContainer.width;
+            this.lastValue.h = this.modifyCtrl.objContainer.height;
+            this.lastValue.matrix= tmpM.getMatrixStr();
+            imgComp.value.matrix = this.lastValue.matrix;
+            imgComp.value.w = this.lastValue.w;
+            imgComp.value.h = this.lastValue.h;
+        });
+    }
+
+
+    async croppImage(id :string) {    
+        const imgComp = this.store.compMap[id]
+        if (imgComp.compKey != "Image") return;
+
+        this.state.compId = id;
+        this.state.visible = true;
+
+        this.controls.historyCtrl.state.enable = false;
+
+
+        const obj = new CompObject(imgComp);
+        const w = obj.width, h = obj.height; //图片的宽高
+        const r1 = h / w;
+        const imgObj = await createImage(imgComp.value.url)
+        const r2 = imgObj.height / imgObj.width;
+
+        const selecCtrl = this.controls.selectCtrl;
+        const cardBox = selecCtrl.getCurrCardBox();
+        const pageBox = selecCtrl.getPageBox();
+
+        const yoff = cardBox.top - pageBox.top;
+        this.modifyCtrl.state.baseTop = yoff;
+        this.modifyCtrl.baseBox.x = cardBox.left;
+        this.modifyCtrl.baseBox.y = cardBox.top;
+        this.modifyCtrl.baseBox.w = cardBox.width;
+        this.modifyCtrl.baseBox.h = cardBox.height;
+        this.modifyCtrl.state.toolbarNames = ["Right", "Cross"];
+
+        const defaultScale =  imgComp.value.s == undefined || imgComp.value.s == 1 && imgComp.value.x == 0 && imgComp.value.y;
+        let scale = 1;
+        let offsetX = 0;
+        let offsetY = 0;
+        let srcWidth = w;
+        let srcHeight = h;
+        let srcOffx = 0;
+        let srcOffy = 0;
+
+        if (r1 < r2) {
+            const s = h / imgObj.height
+            srcWidth = imgObj.width * s;
+            srcOffx = (w - srcWidth) /2.0;
+        }  else {
+            //高度不变,计算宽度
+            const s = w / imgObj.width
+            srcHeight = s * imgObj.height;
+            srcOffy = (h - srcHeight) / 2.0;
+        }
+
+        if (!defaultScale) {//已经有配置了
+            scale = imgComp.value.s;
+            offsetX = imgComp.value.x
+            offsetY = imgComp.value.y;
+        } else {//没有配置 catain 居中处理
+            const maxw = Math.max(w, h) / Math.min(w,h);
+            scale = maxw
+        }   
+
+        let m = new Matrix();
+        if (imgComp.value.matrix) {
+            m.setMatrixStr(imgComp.value.matrix);
+            srcWidth = imgComp.value.w;
+            srcHeight = imgComp.value.h;
+            this.initValue.matrix = imgComp.value.matrix;
+        } else {
+            const imgSrc = new ObjsContainer([]);
+            imgSrc.rect.width = srcWidth;
+            imgSrc.rect.height = srcHeight;
+            imgSrc.translate(srcOffx, srcOffy);
+            imgSrc.setPivot(4);
+            imgSrc.scale(scale, scale)
+            imgSrc.translate(offsetX, offsetY)
+            m = imgSrc.parent.worldTransform.clone();
+            this.initValue.matrix = m.getMatrixStr();
+        }
+
+        this.initValue.w = srcWidth;
+        this.initValue.h = srcHeight;
+
+         //获取obj的image图片的世界大小
+         const objC =  new ObjsContainer([obj]) //当前对象作为容器
+        m.prepend(objC.parent.worldTransform);
+        
+        if (this.controls.selectCtrl.assistMagnet) {
+            this.controls.selectCtrl.assistMagnet.enable = false;
+        }
+
+        this.modifyCtrl.state.showScaleBridge = false;
+        this.modifyCtrl.state.visible = true;
+        this.modifyCtrl.objContainer.rect.width = srcWidth;
+        this.modifyCtrl.objContainer.rect.height = srcHeight;
+        this.modifyCtrl.objContainer.parent.transform.setFromMatrix(m);
+        this.modifyCtrl.objContainer.parent.updateTransform();
+        this.modifyCtrl.updateState();
+
+        this.currSelected = this.store.selected.slice(0);
+
+        this.store.selected = [];
+    }
+
+    close(): void {
+        if ( !this.state.visible  ) return;
+
+        const compId = this.state.compId
+        const imgComp = this.store.compMap[compId];
+        if (imgComp) {
+            imgComp.value.matrix = this.initValue.matrix;
+            imgComp.value.w = this.initValue.w;
+            imgComp.value.h = this.initValue.h;
+        }
+        this.state.compId = "";
+        this.state.visible = false;
+    }
+}
+

+ 4 - 0
src/modules/editor/controllers/HistoryCtrl/index.ts

@@ -2,12 +2,14 @@ import { AnyFun } from "queenjs/typing";
 import { EditorModule } from "../../module";
 import { Action, HistoryController } from "./HistoryController";
 import { get } from "lodash";
+import { reactive } from "vue";
 
 export class HistoryCtrl {
   history: HistoryController;
   historyActionDoing = false;
   historyCombine = false;
   safeList = ["layout.size"];
+  state = reactive({enable: true});
 
   constructor(protected module: EditorModule, historyTotal = 50) {
     this.history = new HistoryController();
@@ -21,6 +23,8 @@ export class HistoryCtrl {
     value: any,
     oldValue: any
   ) {
+    if (!this.state.enable) return;
+
     if (this.historyActionDoing) {
       if (
         type === "set" && 

+ 4 - 0
src/modules/editor/controllers/HotKeyCtrl/index.ts

@@ -43,6 +43,8 @@ export class HotKeyCtrl extends ModuleControl<EditorModule> {
     {
       hotKey: "ctrl+z",
       action() {
+        if (!this.controls.historyCtrl.state.enable) return;
+
         this.controls.historyCtrl.history.undo();
       },
     },
@@ -50,6 +52,8 @@ export class HotKeyCtrl extends ModuleControl<EditorModule> {
     {
       hotKey: "ctrl+shift+z",
       action() {
+        if (!this.controls.historyCtrl.state.enable) return;
+        
         this.controls.historyCtrl.history.redo();
       },
     }, //移动

+ 3 - 0
src/modules/editor/controllers/MediaCtrl/indext.ts

@@ -19,4 +19,7 @@ export class MediaCtrl extends ModuleControl<EditorModule> {
       }
     }
   }
+  removeMedia(currKey: string) {
+    this.state.medias.delete(currKey);
+  }
 }

+ 3 - 2
src/modules/editor/controllers/SelectCtrl/ObjsContainer.ts

@@ -220,7 +220,8 @@ export class ObjsContainer {
     }
 
     applyChildWidth(option:{scaleX?:number, scaleY?:number}) {
-        // if (this.selected.length != 1) return;
+        if (this.selected.length < 1) return;
+        
         const obj = this.selected[0];
 
         //先移除
@@ -299,7 +300,7 @@ export class ObjsContainer {
         let n = this.selected.length;
         while (n--) {
             let child = this.selected[n];
-            child.comp.layout.transformMatrix = child.worldTransform.getMatrixStr();
+            if (child.comp)  child.comp.layout.transformMatrix = child.worldTransform.getMatrixStr();
         }
     }
 }

+ 5 - 3
src/modules/editor/controllers/SelectCtrl/assistMagnetCtrl.ts

@@ -8,6 +8,7 @@ import { CompObject } from "./compObj";
  */
 export class AssistMagnetCtrl { 
     ctrl: SelectCtrl
+    enable = true;
 
     constructor(ctrl: SelectCtrl) {
         this.ctrl = ctrl;
@@ -60,10 +61,11 @@ export class AssistMagnetCtrl {
     }
 
     test(e:MouseEvent) {
-
         this.clientX = e.clientX;
         this.clientY = e.clientY;
-    
+        
+        if ( !this.enable ) return;
+
         const eps = 6;
         const Objc = this.ctrl.objContainer as ObjsContainer
         const bund = Objc.getBound();
@@ -164,7 +166,7 @@ export class AssistMagnetCtrl {
     }
 
     draw() {
-        if ( !(this.currRefXs.length > 0 || this.currRefYs.length > 0) ) {
+        if (!this.enable || !(this.currRefXs.length > 0 || this.currRefYs.length > 0) ) {
             return;
         }
 

+ 5 - 1
src/modules/editor/controllers/SelectCtrl/index.ts

@@ -72,6 +72,10 @@ export class SelectCtrl extends ModuleControl<EditorModule> {
   assistRuler?: AssistRulerCtrl;
   assistMagnet?: AssistMagnetCtrl;
 
+  getPageBox() {
+    return (this.pageEl as (HTMLElement)).getBoundingClientRect();
+  }
+  
   getCurrCardBox() {
     return this.store.currStreamCard.$el.getBoundingClientRect();
   } 
@@ -99,7 +103,7 @@ export class SelectCtrl extends ModuleControl<EditorModule> {
     }
     return out;
   }
-  
+
   initEvents(
     pageEl: HTMLElement,
     selCanvas: HTMLCanvasElement,

+ 240 - 0
src/modules/editor/controllers/TransformCtrl/index.ts

@@ -0,0 +1,240 @@
+import { Events } from "queenjs";
+import { reactive } from "vue";
+import OnMouseDown from "./mouseDown";
+import { ObjsContainer } from "../SelectCtrl/ObjsContainer";
+import { CONST, downOptions, moveOptions, rotateOptions, scaleOptions } from "./state";
+import { Matrix } from "../SelectCtrl/matrix";
+import { Project, VectorLenth } from "../SelectCtrl/objects/mathUtils";
+
+export class TransformCtrl extends Events {
+  state = reactive({
+    toolbarNames: [] as string[], //当前toolbars的名称
+    showScaleBridge: true,
+    showScaletop: true,
+    showScalebottom: true,
+    
+    matrixInvert: 'matrix(1,0,0,1,0,0)',
+    relWidth: 0,
+    relHeight: 0,
+    width: 100,
+    height: 100,
+    matrix: 'matrix(1,0,0,1,0,0)',
+    visible: true,
+    baseTop: 0,
+    bbox: {x:0, y: 0, w: 0, h:0},
+  })
+  //所有toolbar组件
+  toolbars = {} as {[key :string]: (props: any)=>JSX.Element};
+
+  minBox ?:{x:number, y:number, w:number, h:number}
+  maxBox ?:{x:number, y:number, w:number, h:number}
+  baseBox = {x:0, y:0, w: 0, h: 0}
+
+  objContainer = new ObjsContainer([]);
+
+  onClickToolbar(toolName:string) {
+    this.emit("onClickToolbar", toolName);
+  }
+  setToolbar(toolbars: string[]) {
+     this.state.toolbarNames = toolbars;
+  }
+  setToolBarComps(barComps: {[key :string]: ()=>JSX.Element}) {
+    this.toolbars = barComps;
+  }
+
+  initUI(ui: HTMLElement) {
+
+     ui.addEventListener("mousedown", (e:MouseEvent)=>{
+        e.preventDefault();
+        e.stopPropagation();
+        OnMouseDown(this, e);
+     });
+  }
+
+  moving(e:MouseEvent) {
+    const objContainer = this.objContainer;
+    if (moveOptions._initMovePos.x == -1 && moveOptions._initMovePos.y == -1) {
+      moveOptions._initMovePos = { x: objContainer.parent.x, y: objContainer.parent.y };
+    }
+    objContainer.setPivot(0);
+    const dtx = e.clientX - moveOptions._movePreClientX;
+    const dty = e.clientY - moveOptions._movePreClientY;
+    objContainer.translate(
+      dtx,
+      dty
+    );
+    this.updateState();
+    this.emit("moving", dtx, dty);
+  }
+
+  rotate(e:MouseEvent) {
+
+    let StartX = e.clientX - this.baseBox.x;
+    let StartY = e.clientY - this.baseBox.y;
+
+    const objContainer = this.objContainer as ObjsContainer;
+
+    //获取当前屏幕坐标和选框中心点坐标,计算旋转值
+    if (!rotateOptions.rotateCenter) {
+      //let rect = this.objContainer.parent.getBounds(false);
+      let center = objContainer.setPivot(4);
+      rotateOptions.rotateCenter = center;
+
+      let vec = { x: StartX - center.x, y: StartY - center.y };
+      let angle = Math.atan2(vec.y, vec.x);
+      if (angle < 0) angle += 2 * Math.PI;
+      rotateOptions.ratatePre = angle;
+      rotateOptions.objinitAngleRad = objContainer.parent.rotation;
+      rotateOptions.rotateCmd = true;
+      return;
+    }
+
+    let center = rotateOptions.rotateCenter;
+    let vec = { x: StartX - center.x, y: StartY - center.y };
+    let angle = Math.atan2(vec.y, vec.x);
+    if (angle < 0) angle += 2 * Math.PI;
+
+    let dta = rotateOptions.objinitAngleRad + angle - rotateOptions.ratatePre;
+    if (e.shiftKey) {
+      //规整到0 90 180 270
+      if (dta < 0) dta += 2 * Math.PI;
+      let Deg45 = Math.PI / 4.0;
+      let Deg90 = Math.PI / 2.0;
+      let Deg135 = Deg45 * 3;
+      let Deg225 = Deg45 * 5;
+      let Deg270 = Deg45 * 6;
+      let Deg315 = Deg45 * 7;
+      if (dta < Deg45) {
+        dta = 0;
+      } else if (dta < Deg135) {
+        dta = Deg90;
+      } else if (dta < Deg225) {
+        dta = Math.PI;
+      } else if (dta < Deg315) {
+        dta = Deg270;
+      } else {
+        dta = 0;
+      }
+    }
+    rotateOptions.lastRad = dta;
+    objContainer.rotate(dta);
+
+    this.updateState();
+  }
+  scale(event:MouseEvent) {
+
+    let dirIndexs = [
+      "scaleBottomright",
+      "scaleBottomleft",
+      "scaleTopleft",
+      "scaleTopright",
+    ];
+    let dirOrth = ["scaleright", "scaleleft", "scalebottom", "scaletop"];
+
+    let StartX = event.clientX - this.baseBox.x;
+    let StartY = event.clientY - this.baseBox.y;
+    const objContainer = this.objContainer as ObjsContainer;
+
+    //获取当前屏幕坐标和选框中心点坐标,计算旋转值
+    if (!scaleOptions.scalePivot) {
+      let dir = downOptions._mouseDownFlag;
+      let scaleIndex = dirIndexs.indexOf(dir);
+      if (scaleIndex == -1) {
+        scaleIndex = dirOrth.indexOf(dir);
+        if (scaleIndex == 2) scaleIndex = 0;
+      }
+      let pivot = objContainer.setPivot(scaleIndex);
+
+      scaleOptions.scaleIndex = scaleIndex;
+      scaleOptions.scalePivot = pivot;
+
+      scaleOptions.mainAxisVector = { x: StartX - pivot.x, y: StartY - pivot.y };
+      let scale = objContainer.parent.scale;
+      scaleOptions.initScale = { x: scale.x, y: scale.y };
+      scaleOptions.initScaleWith = { w: objContainer.width, h: objContainer.height };
+
+      scaleOptions.mainAxisVectorLenth = VectorLenth(
+        scaleOptions.mainAxisVector.x,
+        scaleOptions.mainAxisVector.y
+      );
+
+      let ret = objContainer.getPivotXY(scaleIndex);
+
+      scaleOptions.xAxisVector = ret.x;
+      scaleOptions.xAxisVectorLength = VectorLenth(ret.x.x, ret.x.y);
+      scaleOptions.yAxisVector = ret.y;
+      scaleOptions.yAxisVectorLength = VectorLenth(ret.y.x, ret.y.y);
+      return;
+    }
+
+    scaleOptions.scaleCmd = true;
+
+    let center = scaleOptions.scalePivot;
+    let vec = { x: StartX - center.x, y: StartY - center.y };
+
+    if (event.shiftKey) {
+      //按住shift 自由缩放
+      let dtaX = Project(vec, scaleOptions.xAxisVector) / scaleOptions.xAxisVectorLength;
+      let dtaY = Project(vec, scaleOptions.yAxisVector) / scaleOptions.yAxisVectorLength;
+      scaleOptions.lastScale.x = dtaX * scaleOptions.initScale.x;
+      scaleOptions.lastScale.y = dtaY * scaleOptions.initScale.y;
+      //   objContainer.scale(this.lastScale.x, this.lastScale.y);
+      const currW = scaleOptions.initScaleWith.w * scaleOptions.lastScale.x;
+      objContainer.scaleSize(currW, scaleOptions.lastScale.y * scaleOptions.initScaleWith.h);
+    } else {
+      let mainVec = scaleOptions.mainAxisVector;
+      let dtaScale = Project(vec, mainVec) / scaleOptions.mainAxisVectorLenth;
+      console.log("dtaScale=>", dtaScale);
+
+      let i = dirOrth.indexOf(downOptions._mouseDownFlag);
+      if (i == -1) {
+        scaleOptions.lastScale.x = dtaScale * scaleOptions.initScale.x;
+        scaleOptions.lastScale.y = dtaScale * scaleOptions.initScale.y;
+
+        if (downOptions._state ==CONST.MODE_SCALE_SCALE) {
+          objContainer.scale(scaleOptions.lastScale.x, scaleOptions.lastScale.y);
+        } else {
+          objContainer.scaleSize(scaleOptions.lastScale.x, scaleOptions.lastScale.y);
+        }
+      } else if (i == 0 || i == 1) {
+        scaleOptions.lastScale.x = dtaScale * scaleOptions.initScale.x;
+        // objContainer.scaleX(this.lastScale.x);
+        objContainer.scaleWidth(scaleOptions.lastScale.x);
+      } else if (i == 2 || i == 3) {
+        scaleOptions.lastScale.y = dtaScale * scaleOptions.initScale.y;
+        // objContainer.scaleY(this.lastScale.y);
+        objContainer.scaleHeight(scaleOptions.lastScale.y);
+      }
+    }
+    this.updateState();
+  }
+
+  updateState() {
+    const selector = this.objContainer;
+    let obj = selector.parent;
+    let tmp = new Matrix();
+    tmp.copyFrom(obj.worldTransform);
+    let matrix = `matrix(${tmp.a},${tmp.b},${tmp.c},${tmp.d},${tmp.tx},${tmp.ty})`;
+
+    tmp.rotate(-tmp.getRotate());
+    tmp.invert();
+    let matrixInvert = `matrix(${tmp.a},${tmp.b},${tmp.c},${tmp.d},0,0)`;
+    this.state.matrix = matrix;
+    this.state.matrixInvert = matrixInvert;
+
+    const w = selector.rect.width, h = selector.rect.height;
+    this.state.width = w;
+    this.state.height = h;
+    this.state.relWidth = w * obj.scale.x;
+    this.state.relHeight = h * obj.scale.y;
+
+    const r = this.objContainer.getBound();
+
+    this.state.bbox.x = r.left;
+    this.state.bbox.y = r.top;
+    this.state.bbox.w = r.width;
+    this.state.bbox.h = r.height;
+
+    this.emit("change");
+  }
+}

+ 79 - 0
src/modules/editor/controllers/TransformCtrl/mouseDown.ts

@@ -0,0 +1,79 @@
+import { TransformCtrl } from ".";
+import OnMouseMove from "./mouseMove";
+import OnMouseUp from "./mouseUp";
+import { CONST, downOptions, moveOptions } from "./state";
+
+function getDivId(div: HTMLElement) {
+  let c: any = div;
+  if (!c) return;
+  let i = 0;
+  do {
+    if (c.id) return c.id;
+    c = c.parentElement;
+    i += 1;
+    if (i > 5) {
+      return;
+    }
+  } while (c);
+}
+
+function getDivTransformFlag(div: HTMLElement) {
+  const id = getDivId(div);
+  if (!id) return "";
+  if (
+    id.indexOf("rotate") > -1 ||
+    id.indexOf("move") > -1 ||
+    id.indexOf("scale") > -1 ||
+    id.indexOf("toolbar") > -1 ||
+    id.indexOf("ruler") > -1
+  )
+    return id;
+
+  return "";
+}
+
+export default function OnMouseDown(ctrl: TransformCtrl, e: MouseEvent) {
+  downOptions._mouseDownTimestamp = Date.now();
+
+
+  function OnMove(e:MouseEvent) {
+    e.preventDefault();
+    e.stopPropagation();
+    OnMouseMove(ctrl, e);
+  }
+  document.addEventListener("mousemove", OnMove);
+
+  //mouseup和click都会被触发, 监听click事件可以阻止子组件的点击行为
+  document.addEventListener("click",(e: MouseEvent) => {
+        document.removeEventListener("mousemove", OnMove);
+        OnMouseUp(ctrl, e);
+    },
+    {
+      capture: true,
+      once: true,
+    }
+  );
+
+  downOptions._state = CONST.MODE_NONE;
+  downOptions._downClientX = e.clientX;
+  downOptions._downClientY = e.clientY;
+
+  downOptions._downed = true;
+  downOptions._mouseDownFlag = getDivTransformFlag(e.target as any);
+  if (downOptions._mouseDownFlag == "rotate") {
+    downOptions._state = CONST.MODE_ROTATE;
+  } else if (downOptions._mouseDownFlag.indexOf("move") > -1) {
+    downOptions._state = CONST.MODE_MOVING;
+  } else if (downOptions._mouseDownFlag.indexOf("scale") > -1) {
+    downOptions._state = CONST.MODE_SCALE_WIDTH;
+  }
+
+  
+  console.log( downOptions._mouseDownFlag ); 
+
+  moveOptions._initMovePos.x = -1;
+  moveOptions._initMovePos.y = -1;
+
+  moveOptions._movePreClientY = e.clientY;
+  moveOptions._movePreClientX = e.clientX;
+}

+ 19 - 0
src/modules/editor/controllers/TransformCtrl/mouseMove.ts

@@ -0,0 +1,19 @@
+import { TransformCtrl } from ".";
+import { CONST, downOptions, moveOptions } from "./state";
+
+export default function OnMouseMove(ctrl:TransformCtrl,  e:MouseEvent) {
+    switch (downOptions._state) {
+      case CONST.MODE_MOVING:
+        ctrl.moving(e);
+        break;
+      case CONST.MODE_ROTATE:
+        ctrl.rotate(e);
+        break;
+      case CONST.MODE_SCALE_WIDTH:
+      case CONST.MODE_SCALE_SCALE:
+        ctrl.scale(e);
+        break
+    }
+    moveOptions._movePreClientY = e.clientY;
+    moveOptions._movePreClientX = e.clientX;
+}

+ 66 - 0
src/modules/editor/controllers/TransformCtrl/mouseUp.ts

@@ -0,0 +1,66 @@
+import { TransformCtrl } from ".";
+import { CONST, downOptions, rotateOptions, scaleOptions } from "./state";
+
+export default function OnMouseUp(ctrl:TransformCtrl,  e:MouseEvent) {
+    let isClick = false;
+    let offsetT = Date.now() - downOptions._mouseDownTimestamp;
+    const dx = Math.abs(e.clientX - downOptions._downClientX);
+    const dy = Math.abs(e.clientY - downOptions._downClientY);
+    if (dx < 2 && dy < 2 && offsetT < 200) {
+      isClick = true;
+    }
+    if (isClick) {
+        ctrl.emit("click");
+
+        const getToolbar = function (div: HTMLElement) {
+            let c: any = div;
+            if (!c) return;
+            let i = 0;
+            do {
+                if (c.dataset.toolname) return c.dataset.toolname;
+              c = c.parentElement;
+              i += 1;
+              if (i > 10) {
+                return;
+              }
+            } while (c);
+          }
+
+          const toolbar =  getToolbar(e.target as any)
+          if (toolbar) {
+            ctrl.onClickToolbar(toolbar);
+          }
+        return;
+    }
+
+    if (downOptions._state == CONST.MODE_ROTATE) {
+        rotateMouseUp(ctrl, e);
+    } 
+
+    else if (
+        downOptions._state == CONST.MODE_SCALE_WIDTH ||
+        downOptions._state == CONST.MODE_SCALE_SCALE
+    ) {
+        scaleOptions.scalePivot = undefined;
+        scaleOptions.scaleCmd = false;
+    } 
+
+    //else if (this._state == MODE_MOVING) {
+    //   this.moveMouseUp(e, isClick);
+    // } else if (this._state == MODE_RULER_LINE) {
+    //   this.assistRuler?.rulerLineMouseUp(e, isClick)
+    // }else if (this._state == MODE_RULER_DRAG) {
+    //   this.assistRuler?.onDragUp(e)
+    // }
+    downOptions._state = CONST.MODE_NONE;
+}
+
+function rotateMouseUp(ctr: TransformCtrl, e:MouseEvent) {
+   
+    rotateOptions.rotateCenter = undefined;
+    if (!rotateOptions.rotateCmd) return;
+
+    // let last = rotateOptions.lastRad;
+    // let initrad = scope.objinitAngleRad;
+    ctr.objContainer?.setPivot(0);
+}

+ 52 - 0
src/modules/editor/controllers/TransformCtrl/state.ts

@@ -0,0 +1,52 @@
+import { number } from "vue-types";
+
+const CONST = {
+  MODE_SEL_RECT: 1,
+  MODE_MOVING: 2,
+  MODE_ROTATE: 3,
+  MODE_SCALE_WIDTH: 4,
+  MODE_SCALE_SCALE: 5,
+  MODE_RULER_LINE: 6,
+  MODE_RULER_DRAG: 7,
+  MODE_NONE: 0,
+};
+
+const downOptions = {
+  _mouseDownTimestamp: Date.now(),
+  _state: CONST.MODE_NONE,
+  _downClientX: 0,
+  _downClientY: 0,
+  _downed: false,
+  _mouseDownFlag: "",
+};
+
+const moveOptions = {
+    _movePreClientY: 0,
+    _movePreClientX: 0,
+    _initMovePos: {x: -1, y: -1},
+}
+
+const rotateOptions = {
+    rotateCenter: undefined as any,// {x: 0, y: 0}
+    ratatePre: 0,
+    lastRad: 0,
+    rotateCmd: false,
+    objinitAngleRad: 0,
+}
+
+const scaleOptions = {
+    scalePivot: undefined as any,
+    scaleIndex: -1,
+    mainAxisVector: {x: 0, y: 0},
+    initScale: {x: 1, y: 1},
+    initScaleWith: {w: 0, h: 0},
+    mainAxisVectorLenth: 0,
+    xAxisVector:{x: 1, y: 1},
+    xAxisVectorLength: 0,
+    yAxisVector: {x: 1, y: 1},
+    yAxisVectorLength: 0,
+    scaleCmd: false,
+    lastScale: {x:1, y: 1},
+}
+
+export { downOptions , CONST, moveOptions, rotateOptions, scaleOptions};

+ 4 - 0
src/modules/editor/module/actions/edit.ts

@@ -90,6 +90,7 @@ export const editActions = EditorModule.action({
     if (compKey == "Text") {
       this.actions.textFocus(compId, true);
     }
+    this.controls.cropCtrl.close();
   },
 
   // 通过拖拽添加组件到画布
@@ -113,6 +114,7 @@ export const editActions = EditorModule.action({
       this.actions.selectObjs([]);
       this.actions.textFocus(currComp.id, true);
     }
+    this.controls.cropCtrl.close();
   },
   
    async selectObjs(ids: string[]) {
@@ -410,6 +412,8 @@ export const editActions = EditorModule.action({
     this.store.setCurrComp(groupComp.children.default?.[0] as string);
   },
 
+ 
+
   handleSelectMoving(key:string) {
     if (this.store.selected.length < 1) return;
     let x = 0, y = 0;

+ 8 - 0
src/modules/editor/module/actions/editWithManualHistory.ts

@@ -41,4 +41,12 @@ export const manualActions = EditorModule.action({
   textFocus(compId:string, focus=true) {
     console.log("text focus", compId,  focus);
   },
+
+  // 取消打组
+  CropImage(image: DesignComp) {
+    // this.controls.transferCtrl.groupCtrl.cancelGroup(groupComp);
+    // this.store.setCurrComp(groupComp.children.default?.[0] as string);
+    console.log("crop Image");
+  },
+
 });

+ 26 - 24
src/modules/editor/module/actions/image.tsx

@@ -8,30 +8,32 @@ export const ImgCompActions = EditorModule.action({
     if (this.store.currComp?.compKey !== "Image") return;
 
     const value = this.store.currComp.value;
-    if (key == "w") {
-      value.y = (parseFloat(value.y) - 0.5).toFixed(2);
-      return;
-    }
-    if (key == "s") {
-      value.y = (parseFloat(value.y) + 0.5).toFixed(2);
-      return;
-    }
-    if (key == "a") {
-      value.x = (parseFloat(value.x) - 0.5).toFixed(2);
-      return;
-    }
-    if (key == "d") {
-      value.x = (parseFloat(value.x) + 0.5).toFixed(2);
-      return;
-    }
-    if (key == "q") {
-      value.s = (parseFloat(value.s) - 0.05).toFixed(2);
-      return;
-    }
-    if (key == "e") {
-      value.s = (parseFloat(value.s) + 0.05).toFixed(2);
-      return;
-    }
+
+    return;
+    // if (key == "w") {
+    //   value.y = (parseFloat(value.y) - 0.5).toFixed(2);
+    //   return;
+    // }
+    // if (key == "s") {
+    //   value.y = (parseFloat(value.y) + 0.5).toFixed(2);
+    //   return;
+    // }
+    // if (key == "a") {
+    //   value.x = (parseFloat(value.x) - 0.5).toFixed(2);
+    //   return;
+    // }
+    // if (key == "d") {
+    //   value.x = (parseFloat(value.x) + 0.5).toFixed(2);
+    //   return;
+    // }
+    // if (key == "q") {
+    //   value.s = (parseFloat(value.s) - 0.05).toFixed(2);
+    //   return;
+    // }
+    // if (key == "e") {
+    //   value.s = (parseFloat(value.s) + 0.05).toFixed(2);
+    //   return;
+    // }
   },
   previewImage(url, previewImageList) {
     if (isWeixinBrowser()) {

+ 2 - 0
src/modules/editor/module/index.ts

@@ -18,6 +18,7 @@ import { SelectCtrl } from "../controllers/SelectCtrl";
 import { CompObject } from "../controllers/SelectCtrl/compObj";
 import { manualActions } from "./actions/editWithManualHistory";
 import { wxController } from "@/controllers/wxController";
+import { ImageCropperCtrl } from "../controllers/CropperCtrl";
 import { MediaCtrl } from "../controllers/MediaCtrl/indext";
 
 export class EditorModule extends ModuleRoot {
@@ -57,6 +58,7 @@ export class EditorModule extends ModuleRoot {
     pickCtrl: new ImagePickController(),
     compUICtrl: new CompUICtrl(this),
     selectCtrl: new SelectCtrl(this),
+    cropCtrl: new ImageCropperCtrl(this),
     mediaCtrl: new MediaCtrl(this),
   };
   compObjsMap = new Map<string, CompObject>();

+ 1 - 0
src/modules/editor/module/stores/index.ts

@@ -17,6 +17,7 @@ export const store = EditorModule.store({
 
     selected: [] as string[], //选中的组件
     selectId: "", //选中的id唯一标识一次选中
+    croppImage:"", //裁剪图片
   }),
   getters: {
     isEditMode(): boolean {

+ 2 - 0
src/modules/editor/objects/Toolbars/CompToolbars.ts

@@ -10,7 +10,9 @@ export const CompToolbars: ICompToolbars = {
     // toolbars.fullWidth,
     toolbars.align,
     toolbars.delete,
+    toolbars.imageCropper,
   ],
+  
   Group: [
     toolbars.cancelGroup,
   ],

+ 10 - 0
src/modules/editor/objects/Toolbars/default.ts

@@ -197,4 +197,14 @@ export const toolbars = createToolbars({
       this.actions.cancelGroupComps(comp);
     },
   },
+  //图片裁剪
+  imageCropper: {
+    component: TipIcons.Cropper,
+    getVisible(comp) {
+      return this.store.currComp.compKey == 'Image';
+    },
+    onClick(comp) {
+      this.controls.cropCtrl.croppImage(this.store.currComp.id);
+    },
+  },
 });

+ 1 - 0
src/pages/website/Promotion2/controller.tsx

@@ -47,6 +47,7 @@ export function createPromotinController(
       {
         title: "编辑分享",
         width: "1000px",
+        destroyOnClose: true,
       }
     );
   }

+ 5 - 0
yarn.lock

@@ -8571,6 +8571,11 @@ vue-cli-plugin-windicss@~1.1.6:
     defu "^6.0.0"
     windicss-webpack-plugin "^1.7.4"
 
+vue-cropper@^1.0.9:
+  version "1.0.9"
+  resolved "http://124.70.149.18:4873/vue-cropper/-/vue-cropper-1.0.9.tgz#de402c57cadc221e9a2063399ff35bb04220ef22"
+  integrity sha512-JhQwxmjqmQohzI7sAp5O/Rfdxuw5HOEYkKjnp/De7iCi6c8Mv6M3N9HpMt9xgWCFchX3/DfXBv2axCZOCg3G8Q==
+
 vue-demi@*, vue-demi@>=0.14.0:
   version "0.14.5"
   resolved "http://124.70.149.18:4873/vue-demi/-/vue-demi-0.14.5.tgz#676d0463d1a1266d5ab5cba932e043d8f5f2fbd9"