lianghongjie 1 year ago
parent
commit
689cbde47d
34 changed files with 625 additions and 231 deletions
  1. 2 2
      src/dict/apis.ts
  2. 38 22
      src/modules/editor/components/CompUI/basicUI/Transfer/index.tsx
  3. 29 2
      src/modules/editor/components/TipIcons/index.ts
  4. 12 8
      src/modules/editor/components/Viewport/Header/index.tsx
  5. 5 6
      src/modules/editor/components/Viewport/Slider/SliderRight/CompTree.tsx
  6. 1 1
      src/modules/editor/controllers/TransferCtrl/transforms/index.ts
  7. 0 20
      src/modules/editor/controllers/TransferCtrl/transforms/move.ts
  8. 0 80
      src/modules/editor/controllers/TransferCtrl/transforms/offset.ts
  9. 38 0
      src/modules/editor/controllers/TransferCtrl/transforms/transform.ts
  10. 25 6
      src/modules/editor/module/actions/edit.ts
  11. 3 1
      src/modules/editor/module/actions/init.ts
  12. 7 7
      src/modules/editor/module/helpers/index.ts
  13. 11 0
      src/modules/editor/module/https/index.ts
  14. 14 5
      src/modules/editor/module/stores/index.ts
  15. 27 12
      src/modules/editor/objects/DesignTemp/DesignComp.ts
  16. 8 5
      src/modules/editor/objects/DesignTemp/creates/createCompStyle.ts
  17. 18 2
      src/modules/editor/objects/DesignTemp/index.ts
  18. 0 13
      src/modules/editor/objects/DesignTemp/versions/0.0.1.ts
  19. 10 1
      src/modules/editor/objects/Toolbars/CompToolbars.ts
  20. 3 6
      src/modules/editor/objects/Toolbars/TreeToolbars.ts
  21. 101 25
      src/modules/editor/objects/Toolbars/default.ts
  22. 3 2
      src/modules/editor/typings.ts
  23. 27 0
      src/modules/resource/actions/promotion.ts
  24. 24 0
      src/modules/resource/controllers/ComponentController.ts
  25. 16 0
      src/modules/resource/helper.ts
  26. 14 0
      src/modules/resource/http.ts
  27. 1 0
      src/modules/resource/index.ts
  28. 3 2
      src/pages/editor/EditPage/index.tsx
  29. 86 0
      src/pages/website/MyComps/components/CompItem.tsx
  30. 18 0
      src/pages/website/MyComps/components/Header.tsx
  31. 45 0
      src/pages/website/MyComps/components/index.tsx
  32. 22 0
      src/pages/website/MyComps/index.tsx
  33. 9 3
      src/pages/website/components/layout/LeftContent.tsx
  34. 5 0
      src/pages/website/router.ts

+ 2 - 2
src/dict/apis.ts

@@ -14,8 +14,8 @@ const Dict_Apis = {
   queentreeLocal: base,
   auth: `${baseURL}${baseVersion}/usercenter`,
   queentree: `${baseURL}${treeVersion}/assetcenter`,
-  promotion: `${baseURL}${baseVersion}/promotion`,
-  // promotion: `${localURL}/promotion`,
+  // promotion: `${baseURL}${baseVersion}/promotion`,
+  promotion: `${localURL}/promotion`,
 };
 
 export { Dict_Apis };

+ 38 - 22
src/modules/editor/components/CompUI/basicUI/Transfer/index.tsx

@@ -60,18 +60,6 @@ export const Transfer = defineComponent({
               width: transferStyle.width,
             }}
           >
-            <div class={toolbarStyle}>
-              {CompToolbars.default.map((item, i) => {
-                if (item.disable?.call(editor, comp)) return;
-                return (
-                  <item.component
-                    class="p-4px"
-                    value={item.getValue?.(comp)}
-                    onClick={() => item.onClick.call(editor, comp)}
-                  />
-                );
-              })}
-            </div>
             <div
               class={borderStyle}
               style={{
@@ -84,12 +72,34 @@ export const Transfer = defineComponent({
               style={{ marginBottom: "-" + transferStyle.height }}
               onMousedown={(e) => transferCtrl.mousedown(e, "resize")}
             ></div>
+            <div class={toolbarStyle}>
+              {CompToolbars.default.map((item, i) => {
+                return item.getVisible.call(editor, comp) ? (
+                  <item.component
+                    class="p-4px"
+                    value={item.getValue?.(comp)}
+                    onClick={() => item.onClick.call(editor, comp)}
+                  />
+                ) : null;
+              })}
+            </div>
+
             <div
-              class={offsetBtnStyle}
+              class={transformBtnsStyle}
               style={{ marginBottom: "-" + transferStyle.height }}
-              onMousedown={(e) => transferCtrl.mousedown(e, "offset")}
             >
-              +
+              <div
+                class={transBtnStyle}
+                onMousedown={(e) => transferCtrl.mousedown(e, "move")}
+              >
+                +
+              </div>
+              <div
+                class={transBtnStyle}
+                onMousedown={(e) => transferCtrl.mousedown(e, "rotate")}
+              >
+                -
+              </div>
             </div>
             {/* {Object.entries(btnStyles).map(([name, style]) => (
             <div
@@ -124,7 +134,7 @@ const borderStyle = css`
   height: 100%;
   outline: 2px solid @inf-primary-color;
   pointer-events: none;
-  z-index: 9;
+  z-index: 999;
 `;
 
 const resizeStyle = css`
@@ -135,7 +145,7 @@ const resizeStyle = css`
   height: 16px;
   border-radius: 50%;
   background-color: #fff;
-  z-index: 9;
+  z-index: 999;
   transform: translate(50%, 50%);
   box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.2);
   cursor: nwse-resize;
@@ -144,10 +154,18 @@ const resizeStyle = css`
   }
 `;
 
-const offsetBtnStyle = css`
+const transformBtnsStyle = css`
+  @apply space-x-5px whitespace-nowrap;
   position: absolute;
   bottom: 0;
   left: 50%;
+  font-size: 16px;
+  z-index: 999;
+  transform: translate(-50%, 40px);
+`;
+
+const transBtnStyle = css`
+  display: inline-block;
   width: 30px;
   height: 30px;
   border-radius: 50%;
@@ -155,9 +173,6 @@ const offsetBtnStyle = css`
   text-align: center;
   line-height: 30px;
   font-size: 16px;
-  z-index: 9;
-  transform: translate(-50%, 40px);
-
   @apply shadow cursor-move;
 
   &:hover {
@@ -175,7 +190,7 @@ const fanBtnStyle = css`
   text-align: center;
   clip-path: polygon(0 0, 100% 0, 100% 50%, 0 50%);
   cursor: grab;
-  z-index: 99;
+  z-index: 999;
 
   &:hover {
     background: @inf-primary-color;
@@ -188,4 +203,5 @@ const toolbarStyle = css`
   top: 0;
   left: 50%;
   transform: translate(-50%, -40px);
+  z-index: 999;
 `;

+ 29 - 2
src/modules/editor/components/TipIcons/index.ts

@@ -1,10 +1,17 @@
 import {
+  IconArrowLeft,
+  IconArrowRight,
+  IconAxis,
+  IconBtnNext,
   IconClear,
+  IconCube,
   IconDelete,
   IconEyeOff,
   IconEyeOn,
   IconLock,
+  IconPlus,
   IconRedo,
+  IconReduce,
   IconShadow,
   IconShadowOff,
   IconUndo,
@@ -13,6 +20,10 @@ import {
 import { createTipIcon } from "./create";
 
 export const TipIcons = {
+  Align: createTipIcon({
+    icons: [IconArrowLeft, IconAxis, IconArrowRight],
+    tips: ["左对齐", "居中", "右对齐"],
+  }),
   Position: createTipIcon({
     icons: [IconShadowOff, IconShadow],
     tips: ["开启浮动", "关闭浮动"],
@@ -29,9 +40,13 @@ export const TipIcons = {
     icons: [IconDelete],
     tips: ["删除"],
   }),
-  ClearOffset: createTipIcon({
+  FullWidth: createTipIcon({
+    icons: [IconCube],
+    tips: ["全屏宽度"],
+  }),
+  ClearTransform: createTipIcon({
     icons: [IconClear],
-    tips: ["清除偏移"],
+    tips: ["清除变换"],
   }),
   undo: createTipIcon({
     icons: [IconUndo],
@@ -41,4 +56,16 @@ export const TipIcons = {
     icons: [IconRedo],
     tips: ["重做"],
   }),
+  LayerUp: createTipIcon({
+    icons: [IconPlus],
+    tips: ["层级上移"],
+  }),
+  LayerDown: createTipIcon({
+    icons: [IconReduce],
+    tips: ["层级下移"],
+  }),
+  ParentComp: createTipIcon({
+    icons: [IconBtnNext],
+    tips: ["切换到父组件"],
+  }),
 };

+ 12 - 8
src/modules/editor/components/Viewport/Header/index.tsx

@@ -13,18 +13,22 @@ export default defineUI({
           value={store.mode}
           onChange={(e) => actions.switchMode(e.target.value)}
         >
-          <Radio.Button value="edit">编辑</Radio.Button>
+          <Radio.Button value={store.isEditPage ? "editPage" : "editComp"}>
+            编辑
+          </Radio.Button>
           <Radio.Button value="preview">预览</Radio.Button>
         </Radio.Group>
 
         <aside class="space-x-10px">
-          <Dropdown
-            overlay={<ShareBox />}
-            trigger="click"
-            placement="bottomRight"
-          >
-            <Button>分享</Button>
-          </Dropdown>
+          {store.isEditPage && (
+            <Dropdown
+              overlay={<ShareBox />}
+              trigger="click"
+              placement="bottomRight"
+            >
+              <Button>分享</Button>
+            </Dropdown>
+          )}
           <Button type="primary" onClick={() => actions.saveDesign()}>
             保存
           </Button>

+ 5 - 6
src/modules/editor/components/Viewport/Slider/SliderRight/CompTree.tsx

@@ -32,7 +32,7 @@ export const CompTree = defineComponent({
               key: comp.id,
               title: CompUI[comp.compKey].options.name,
               value: comp.id,
-              children: getCompChildren(helper.getCompChildIds(comp)),
+              children: getCompChildren(comp.getChildIds()),
             };
           });
         }
@@ -82,9 +82,8 @@ const CompNode = defineComponent({
           <img src={thumbnail} />
           <span class="flex-1">{props.title}</span>
           <span class="space-x-4px">
-            {actions.map((action) => {
-              if (action.disable?.call(editor, comp)) return;
-              return (
+            {actions.map((action) =>
+              action.getVisible.call(editor, comp) ? (
                 <action.component
                   class="p-4px"
                   key={comp.id}
@@ -94,8 +93,8 @@ const CompNode = defineComponent({
                     action.onClick.call(editor, comp);
                   }}
                 />
-              );
-            })}
+              ) : null
+            )}
           </span>
         </div>
       );

+ 1 - 1
src/modules/editor/controllers/TransferCtrl/transforms/index.ts

@@ -1,2 +1,2 @@
 export * from "./resize";
-export * from "./offset";
+export * from "./transform";

+ 0 - 20
src/modules/editor/controllers/TransferCtrl/transforms/move.ts

@@ -1,20 +0,0 @@
-import { TransCreateFn } from "..";
-
-export const move: TransCreateFn = () => ({
-  mousemove(e) {
-    const { compEl, module, transEvent, currComp } = this;
-    const { helper } = module;
-
-    compEl.style.left = helper.designToNaturalSize(
-      currComp.value.position[0] + transEvent.offsetX * 2
-    );
-    compEl.style.top = helper.designToNaturalSize(
-      currComp.value.position[1] + transEvent.offsetY * 2
-    );
-  },
-  mouseup() {
-    const { transEvent, currComp } = this;
-    currComp.value.position[0] += transEvent.offsetX * 2;
-    currComp.value.position[1] += transEvent.offsetY * 2;
-  },
-});

+ 0 - 80
src/modules/editor/controllers/TransferCtrl/transforms/offset.ts

@@ -1,80 +0,0 @@
-import { Layout } from "@/modules/editor/typings";
-import { TransCreateFn, TransferCtrl } from "..";
-
-type Direction = "top" | "bottom" | "x";
-
-const offset_one: TransCreateFn = (direction: Direction) => ({
-  mousemove() {
-    if (this.currComp.layout.margin) return;
-
-    const style = this.module.helper.createStyle({
-      offset: {
-        [direction[0] as "x"]: getOffset.call(this, direction),
-      },
-    });
-    Object.entries(style).forEach(([key, value]: any[]) => {
-      if (key === "transform") {
-        (this.compEl.firstElementChild as HTMLElement).style[key] = value;
-      } else {
-        this.compEl.style[key] = value;
-      }
-    });
-  },
-  mouseup() {
-    if (this.currComp.layout.margin) return;
-    const { layout } = this.currComp;
-
-    const offset: any = layout.offset || (layout.offset = {});
-
-    offset[direction[0]] = getOffset.call(this, direction);
-  },
-});
-
-function getOffset(this: TransferCtrl, direction: Direction) {
-  const { transEvent } = this;
-  const { layout } = this.currComp;
-  let offset = (layout.offset as any)?.[direction[0]] || 0;
-
-  switch (direction) {
-    case "top":
-      offset += transEvent.offsetY * 2;
-      break;
-    case "bottom":
-      offset += transEvent.offsetY * 2;
-      break;
-    case "x":
-      offset += transEvent.offsetX * 2;
-      break;
-  }
-  return offset;
-}
-
-function getOffset2(this: TransferCtrl) {
-  const { transEvent } = this;
-  const { layout } = this.currComp;
-  const offset = { x: 0, y: 0 };
-
-  offset.x = (layout.offset?.x || 0) + transEvent.offsetX * 2;
-  offset.y = (layout.offset?.y || 0) + transEvent.offsetY * 2;
-
-  return offset;
-}
-
-export const offset: TransCreateFn = () => ({
-  mousemove() {
-    const style = this.module.helper.createStyle({
-      offset: getOffset2.call(this),
-    });
-    Object.entries(style).forEach(([key, value]: any[]) => {
-      this.compEl.style[key] = value;
-    });
-  },
-  mouseup() {
-    this.currComp.layout.offset = getOffset2.call(this);
-  },
-});
-
-// export const offset_top = offset_one.bind(null, "top");
-// export const offset_bottom = offset_one.bind(null, "bottom");
-// export const offset_left = offset_one.bind(null, "x");
-// export const offset_right = offset_left;

+ 38 - 0
src/modules/editor/controllers/TransferCtrl/transforms/transform.ts

@@ -0,0 +1,38 @@
+import { TransCreateFn, TransferCtrl } from "..";
+
+function getTransform(this: TransferCtrl, transType: string) {
+  const { transEvent } = this;
+  const { transform = {} } = this.currComp.layout;
+  const offset = {
+    x: transform.x || 0,
+    y: transform.y || 0,
+    r: transform.r || 0,
+  };
+  switch (transType) {
+    case "move":
+      offset.x += transEvent.offsetX * 2;
+      offset.y += transEvent.offsetY * 2;
+      break;
+    case "rotate":
+      offset.r = (offset.r + transEvent.offsetX * -1) % 360;
+  }
+
+  return offset;
+}
+
+const transform: TransCreateFn = (transType: string) => ({
+  mousemove() {
+    const style = this.module.helper.createStyle({
+      transform: getTransform.call(this, transType),
+    });
+    Object.entries(style).forEach(([key, value]: any[]) => {
+      this.compEl.style[key] = value;
+    });
+  },
+  mouseup() {
+    this.currComp.layout.transform = getTransform.call(this, transType);
+  },
+});
+
+export const move = transform.bind(null, "move");
+export const rotate = transform.bind(null, "rotate");

+ 25 - 6
src/modules/editor/module/actions/edit.ts

@@ -2,6 +2,7 @@ import { Exception, queenApi } from "queenjs";
 import { EditorModule } from "..";
 import { ICompKeys } from "../../typings";
 import { DesignComp } from "../../objects/DesignTemp/DesignComp";
+import { CompUI } from "../../components/CompUI";
 
 export const editActions = EditorModule.action({
   // 添加组件到画布
@@ -40,7 +41,9 @@ export const editActions = EditorModule.action({
     try {
       queenApi.showLoading("保存中");
       this.store.clearUnusedComps();
-      await this.https.saveDesign(this.store.designData);
+      await this.https[this.store.isEditPage ? "saveDesign" : "saveComp"](
+        this.store.designData
+      );
       queenApi.messageSuccess("保存成功");
     } catch (error: any) {
       throw Exception.error(error.toString());
@@ -58,13 +61,29 @@ export const editActions = EditorModule.action({
   setCompVisible(comp: DesignComp) {
     comp.layout.visible = comp.layout.visible === false ? true : false;
   },
-  // 清除组件偏移
-  clearCompOffset(comp: DesignComp) {
+  // 清除组件变换
+  clearCompTransform(comp: DesignComp) {
     comp.layout.margin = "";
-    comp.layout.offset = {};
+    comp.layout.transform = {};
   },
   // 设置组件锁定状态
-  setCompLock(comp: DesignComp){
+  setCompLock(comp: DesignComp) {
     //
-  }
+  },
+  // 设置组件层级
+  setCompLayer(comp: DesignComp, offset: number) {
+    comp.layout.zIndex = Math.min(
+      Math.max((comp.layout.zIndex || 0) + offset, 0),
+      99
+    );
+  },
+  // 宽度铺满
+  fullCompWidth(comp: DesignComp) {
+    comp.layout.size || (comp.layout.size = []);
+    comp.layout.size[0] = 750;
+  },
+  //
+  setCompAlign(comp: DesignComp, align: string) {
+    comp.layout.alignSelf = align;
+  },
 });

+ 3 - 1
src/modules/editor/module/actions/init.ts

@@ -15,7 +15,9 @@ export const initActions = EditorModule.action({
 
   // 初始化数据
   async initDesign(id: string) {
-    const ret = await this.https.getDesignDetail(id);
+    const ret = await this.https[
+      this.store.isEditPage ? "getDesignDetail" : "getCompDetail"
+    ](id);
     this.store.initDesignData(ret.result);
   },
   // 切换模式

+ 7 - 7
src/modules/editor/module/helpers/index.ts

@@ -32,14 +32,14 @@ export const helpers = EditorModule.helper({
     getParentComp(compId);
     return comps;
   },
-  getCompChildIds(comp: DesignComp) {
-    return mapValuesDeep(
-      comp.children,
-      (v) => typeof v === "string",
-      (v: string) => v
-    );
-  },
   createStyle(layout: Partial<Layout>) {
     return createCompStyle(this, layout);
   },
+  isCustomChildComp(comp: DesignComp): boolean {
+    const parentComp = this.helper.findParentComp(comp.id);
+    if (!parentComp) return false;
+    const i =
+      parentComp.children.default?.findIndex((d) => d === comp.id) ?? -1;
+    return i >= 0;
+  },
 });

+ 11 - 0
src/modules/editor/module/https/index.ts

@@ -7,10 +7,21 @@ export const https = EditorModule.http({
       method: "GET",
     });
   },
+  getCompDetail(id: string) {
+    return this.request("/frame/detail/" + id, {
+      method: "GET",
+    });
+  },
   saveDesign(data: DesignTemp) {
     return this.request("/h5/update", {
       method: "POST",
       data,
     });
   },
+  saveComp(data: DesignTemp) {
+    return this.request("/frame/update", {
+      method: "POST",
+      data,
+    });
+  },
 });

+ 14 - 5
src/modules/editor/module/stores/index.ts

@@ -9,13 +9,22 @@ import { EditorMode, ICompKeys } from "../../typings";
 
 export const store = EditorModule.store({
   state: () => ({
-    mode: "edit" as EditorMode,
+    mode: "editPage" as EditorMode,
     currCompId: "root",
     designData: new DesignTemp(),
   }),
   getters: {
-    isEditMode(state) {
-      return state.mode === "edit";
+    isEditMode(): boolean {
+      return !this.store.isPreview;
+    },
+    isEditPage(state) {
+      return state.mode === "editPage";
+    },
+    isEditComp(state) {
+      return state.mode === "editComp";
+    },
+    isPreview(state) {
+      return state.mode === "preview";
     },
     currComp(state) {
       return state.designData.compMap[state.currCompId];
@@ -70,7 +79,7 @@ export const store = EditorModule.store({
 
       if (deleteOK) {
         const comp = this.helper.findComp(compId) as DesignComp;
-        const ids = this.helper.getCompChildIds(comp);
+        const ids = comp.getChildIds();
         [compId, ...ids].forEach((id) => {
           delete compMap[id];
         });
@@ -88,7 +97,7 @@ export const store = EditorModule.store({
           const comp = this.helper.findComp(id);
           if (!comp) return;
           used.add(id);
-          getUsedIds(this.helper.getCompChildIds(comp));
+          getUsedIds(comp.getChildIds());
         });
         return used;
       };

+ 27 - 12
src/modules/editor/objects/DesignTemp/DesignComp.ts

@@ -1,6 +1,7 @@
+import { cloneDeep, isEmpty } from "lodash";
 import { nanoid } from "nanoid";
 import { Background, ICompKeys, Layout } from "../../typings";
-import { cloneDeep } from "lodash";
+import { mapValuesDeep } from "@/utils";
 
 export class DesignComp {
   declare pid: string; // pid 作为前端临时数据,不存储到服务器,在初始化时关联
@@ -14,17 +15,31 @@ export class DesignComp {
 
   constructor(data?: Partial<DesignComp>) {
     if (!data) return;
-    const newData = cloneDeep(filterObj(data));
-    Object.assign(this, newData);
+    if (data instanceof DesignComp) return data;
+    const fromData = Object.fromEntries(
+      Object.entries(data).filter(
+        ([_, value]) => value !== null && value !== undefined
+      )
+    );
+    Object.assign(this, cloneDeep(fromData));
+  }
+
+  getChildIds() {
+    return mapValuesDeep(
+      this.children,
+      (v) => typeof v === "string",
+      (v: string) => v
+    );
   }
-}
 
-function filterObj(obj: any) {
-  const filteredObj: any = {};
-  Object.keys(obj).forEach((key) => {
-    if (obj[key] !== null && obj[key] !== undefined) {
-      filteredObj[key] = obj[key];
-    }
-  });
-  return filteredObj;
+  get isPostioned() {
+    return this.layout.position === "absolute";
+  }
+  get isTransformed() {
+    return !isEmpty(this.layout.transform);
+  }
+  get isFullWidth() {
+    const w = this.layout.size?.[0];
+    return !w || w === 750;
+  }
 }

+ 8 - 5
src/modules/editor/objects/DesignTemp/creates/createCompStyle.ts

@@ -19,12 +19,15 @@ export function createCompStyle(module: EditorModule, layout: Layout) {
 
   if (layout.margin) {
     style.margin = layout.margin;
-  } else if (layout.offset) {
-    if (layout.offset.x) {
-      transform.translateX = designToNaturalSize(layout.offset.x);
+  } else if (layout.transform) {
+    if (layout.transform.x) {
+      transform.translateX = designToNaturalSize(layout.transform.x);
     }
-    if (layout.offset.y) {
-      style.marginTop = designToNaturalSize(layout.offset.y);
+    if (layout.transform.y) {
+      style.marginTop = designToNaturalSize(layout.transform.y);
+    }
+    if (layout.transform.r) {
+      transform.rotate = layout.transform.r + "deg";
     }
   }
 

+ 18 - 2
src/modules/editor/objects/DesignTemp/index.ts

@@ -1,4 +1,4 @@
-
+import { mapValuesDeep } from "@/utils";
 import { DesignComp } from "./DesignComp";
 import { dataTransform } from "./versions/0.0.1";
 
@@ -7,11 +7,27 @@ export class DesignTemp {
   _id!: string;
   title = "";
   pageStyle?: any;
-  // content: string[] = [];
+  content: string[] = ["root"];
   compMap: { [compId: string]: DesignComp } = {};
 
   constructor(data?: Partial<DesignTemp>) {
     if (!data) return;
     Object.assign(this, dataTransform(data));
+    // 初始化DesignComp
+    Object.entries(this.compMap).forEach(([key, value]) => {
+      this.compMap[key] = new DesignComp(value);
+    });
+    // 初始化DesignComp的pid
+    Object.values(this.compMap).forEach((comp) => {
+      const childIds = mapValuesDeep(
+        comp.children,
+        (v) => typeof v === "string",
+        (v: string) => v
+      );
+      childIds.forEach((cid) => {
+        const childComp = this.compMap[cid];
+        Object.defineProperty(childComp, "pid", { value: comp.id });
+      });
+    });
   }
 }

+ 0 - 13
src/modules/editor/objects/DesignTemp/versions/0.0.1.ts

@@ -1,6 +1,5 @@
 import { CompUI } from "@/modules/editor/components/CompUI";
 import { addCacheToMap } from "@/modules/editor/components/CompUI/defines/createCompId";
-import { mapValuesDeep } from "@/utils";
 import { set } from "lodash";
 import { DesignComp } from "../DesignComp";
 
@@ -31,18 +30,6 @@ export function dataTransform(data: any) {
     });
   }
 
-  Object.values(compMap).forEach((comp) => {
-    const childIds = mapValuesDeep(
-      comp.children,
-      (v) => typeof v === "string",
-      (v: string) => v
-    );
-    childIds.forEach((cid) => {
-      const childComp = compMap[cid];
-      Object.defineProperty(childComp, "pid", { value: comp.id });
-    });
-  });
-
   return data;
 }
 

+ 10 - 1
src/modules/editor/objects/Toolbars/CompToolbars.ts

@@ -1,5 +1,14 @@
 import { ICompToolbars, toolbars } from "./default";
 
 export const CompToolbars: ICompToolbars = {
-  default: [toolbars.position, toolbars.clearOffset, toolbars.delete],
+  default: [
+    toolbars.parentComp,
+    toolbars.position,
+    toolbars.layerUp,
+    toolbars.layerDown,
+    toolbars.clearTransform,
+    toolbars.fullWidth,
+    toolbars.align,
+    toolbars.delete,
+  ],
 };

+ 3 - 6
src/modules/editor/objects/Toolbars/TreeToolbars.ts

@@ -2,12 +2,9 @@ import { ICompToolbars, toolbars } from "./default";
 
 export const TreeToolbars: ICompToolbars = {
   default: [
-    {
-      ...toolbars.position,
-      disable(comp) {
-        return comp.layout.position !== "absolute";
-      },
-    },
+    toolbars.position.setVisible(function (comp) {
+      return comp.isPostioned;
+    }),
     toolbars.visible,
   ],
   Page: [],

+ 101 - 25
src/modules/editor/objects/Toolbars/default.ts

@@ -1,62 +1,138 @@
-import { isEmpty } from "lodash";
 import { TipIcons } from "../../components/TipIcons";
 import { EditorModule } from "../../module";
 import { ICompKeys } from "../../typings";
 import { DesignComp } from "../DesignTemp/DesignComp";
 
-type ToolbarItem = {
+function getVisible(this: EditorModule, comp: DesignComp) {
+  return true;
+}
+
+type ItemParams = Pick<ToolbarItem, "getValue" | "component" | "onClick"> & {
+  getVisible?: typeof getVisible;
+};
+class ToolbarItem {
   component: any;
   getValue?: (c: DesignComp) => number;
-  disable?: (this: EditorModule, c: DesignComp) => boolean;
   onClick: (this: EditorModule, c: DesignComp) => void;
-};
+  getVisible!: typeof getVisible;
+
+  constructor(data: ItemParams) {
+    this.component = data.component;
+    this.getValue = data.getValue;
+    this.onClick = data.onClick;
+    this.getVisible = data.getVisible || getVisible;
+    return;
+  }
+
+  setVisible(cb: typeof getVisible) {
+    const item = new ToolbarItem(this);
+    item.getVisible = cb;
+    return item;
+  }
+}
 
 export type ICompToolbars = { [name in ICompKeys]?: ToolbarItem[] } & {
   default: ToolbarItem[];
 };
 
-function createToolbars<T extends Record<string, ToolbarItem>>(obj: T) {
-  return obj;
+function createToolbars<T extends Record<string, ItemParams>>(obj: T) {
+  const data: any = {};
+  Object.entries(obj).forEach(([key, value]) => {
+    data[key] = new ToolbarItem(value);
+  });
+  return data as { [name in keyof T]: ToolbarItem };
 }
 
 export const toolbars = createToolbars({
-  position: {
-    component: TipIcons.Position,
-    getValue: (comp) => (comp.layout.position === "absolute" ? 1 : 0),
+  // 显示/隐藏
+  visible: {
+    component: TipIcons.Visible,
+    getValue: (comp) => (comp.layout.visible !== false ? 0 : 1),
     onClick(comp) {
-      this.actions.setCompPosition(comp);
+      this.actions.setCompVisible(comp);
+    },
+  },
+  // 锁定
+  lock: {
+    component: TipIcons.Lock,
+    getValue: (comp) => (comp ? 0 : 1),
+    onClick(comp) {
+      this.actions.setCompLock(comp);
     },
   },
+  // 删除
   delete: {
     component: TipIcons.Delete,
-    disable(comp) {
-      return !this.store.pageCompIds.includes(comp.id);
+    getVisible(comp) {
+      return this.helper.isCustomChildComp(comp);
     },
     onClick(comp) {
       this.actions.removeComp(comp.id);
     },
   },
-  clearOffset: {
-    component: TipIcons.ClearOffset,
-    disable(comp) {
-      return isEmpty(comp.layout.offset);
+  // 对齐
+  align: {
+    component: TipIcons.Align,
+    getVisible: (comp) => !comp.isPostioned && !comp.isFullWidth,
+    getValue: (comp) =>
+      ["start", "center", "end"].indexOf(comp.layout.alignSelf || "start"),
+    onClick(comp) {
+      const vals = ["start", "center", "end"];
+      let index = vals.indexOf(comp.layout.alignSelf || "start");
+      this.actions.setCompAlign(comp, vals[++index % 3]);
     },
+  },
+  // 绝对定位
+  position: {
+    component: TipIcons.Position,
+    getVisible(comp) {
+      return this.helper.isCustomChildComp(comp);
+    },
+    getValue: (comp) => (comp.layout.position === "absolute" ? 1 : 0),
     onClick(comp) {
-      this.actions.clearCompOffset(comp);
+      this.actions.setCompPosition(comp);
     },
   },
-  visible: {
-    component: TipIcons.Visible,
-    getValue: (comp) => (comp.layout.visible !== false ? 0 : 1),
+  // 全屏尺寸
+  fullWidth: {
+    component: TipIcons.FullWidth,
+    getVisible: (comp) => !comp.isTransformed && !comp.isFullWidth,
     onClick(comp) {
-      this.actions.setCompVisible(comp);
+      this.actions.fullCompWidth(comp);
     },
   },
-  lock: {
-    component: TipIcons.Lock,
-    getValue: (comp) => (comp ? 0 : 1),
+  // 清除变换
+  clearTransform: {
+    component: TipIcons.ClearTransform,
+    getVisible: (comp) => comp.isTransformed,
     onClick(comp) {
-      this.actions.setCompLock(comp);
+      this.actions.clearCompTransform(comp);
+    },
+  },
+  // 定位图层上移
+  layerUp: {
+    component: TipIcons.LayerUp,
+    getVisible: (comp) => comp.isPostioned,
+    onClick(comp) {
+      this.actions.setCompLayer(comp, 1);
+    },
+  },
+  // 定位图层下移
+  layerDown: {
+    component: TipIcons.LayerDown,
+    getVisible: (comp) => comp.isPostioned,
+    onClick(comp) {
+      this.actions.setCompLayer(comp, -1);
+    },
+  },
+  // 切换到父组件
+  parentComp: {
+    component: TipIcons.ParentComp,
+    getVisible(comp) {
+      return !this.helper.isCustomChildComp(comp);
+    },
+    onClick(comp) {
+      this.actions.pickParentComp(comp.id);
     },
   },
 });

+ 3 - 2
src/modules/editor/typings.ts

@@ -2,14 +2,15 @@ import { CompUI } from "./components/CompUI";
 
 export type ICompKeys = keyof typeof CompUI;
 
-export type EditorMode = "edit" | "preview";
+export type EditorMode = "editPage" | "editComp" | "preview";
 
 export type Layout = {
   position?: "absolute";
   visible?: boolean;
   size?: number[]; // width height
   alignSelf?: string;
-  offset?: {
+  transform?: {
+    r?: number; // rotate
     x?: number; // translateX
     y?: number; // marginTop
   };

+ 27 - 0
src/modules/resource/actions/promotion.ts

@@ -38,4 +38,31 @@ export const promotionAction = ResourceModule.action({
     const url = `${location.origin}/editor.html#/?id=${res.result}`;
     location.href = url;
   },
+
+  async createComp() {
+    const title = await queenApi.showInput({
+      title: "请输入标题",
+    });
+    if (!title) return;
+    const res = await this.https.createComp({ title });
+    console.log(location.host, location.host == "www.infish.cn")
+    if (location.host == "www.infish.cn") {
+      const url = `${location.origin}/projects/queenshow/eidtComp.html#/?id=${res.result}`;
+      location.href = url;
+      return;
+    }
+
+    const url = `${location.origin}/eidtComp.html#/?id=${res.result}`;
+    location.href = url;
+  },
+
+  async deleteComp(record: any) {
+    const res = await queenApi.showConfirm({
+      content: `删除后无法恢复,确定要删除:${record.title}?`,
+      type: "danger",
+    });
+    if (!res) return;
+    await this.https.deleteComp(record._id);
+    // this.controls.promotionListCtrl.fresh();
+  },
 });

+ 24 - 0
src/modules/resource/controllers/ComponentController.ts

@@ -0,0 +1,24 @@
+import { PageListController } from "@queenjs/controllers";
+
+export class ComponentController {
+  ListCtrl = new PageListController<any, any>();
+  createComp() {
+    console.log("createPromotion");
+  }
+  onMenuClick(menu: string, item: any) {
+    console.log("onMenuClick", menu, item);
+  }
+  onEdit(item: any) {
+    const _params = new URLSearchParams(decodeURIComponent(location.search));
+    const host = _params.get("host");
+
+    if (location.host == "www.infish.cn") {
+      const url = `${location.origin}/projects/queenshow/editor.html?host=${host}#/?id=${item._id}&mode=editComp`;
+      location.href = url;
+      return;
+    }
+
+    const url = `${location.origin}/editor.html?host=${host}#/?id=${item._id}&mode=editComp`;
+    location.href = url;
+  }
+}

+ 16 - 0
src/modules/resource/helper.ts

@@ -2,6 +2,7 @@ import { PageListController } from "@queenjs/controllers";
 import { ResourceModule } from ".";
 import { PromotionController } from "./controllers/PromotionController";
 import { MaterialController } from "./controllers/MaterialController";
+import { ComponentController } from "./controllers/ComponentController";
 
 export const helper = ResourceModule.helper({
   createFileName(fileName: string, dir: string) {
@@ -29,6 +30,21 @@ export const helper = ResourceModule.helper({
     return ctrl;
   },
 
+  createCompController() {
+    const ctrl= new ComponentController();
+    ctrl.ListCtrl = new PageListController(this.config?.httpConfig);
+    ctrl.ListCtrl.setCrudPrefix("/frame")
+    ctrl.createComp = this.actions.createComp;
+    ctrl.onMenuClick = async (name, record) => {
+      if(name == "delete") {
+       await this.actions.deletePromotion(record);
+       ctrl.ListCtrl.fresh();
+      }
+    }
+
+    return ctrl;
+  },
+
   createSourceController(isSelectModel:boolean, selectType :string) {
       const {controls, actions} = this;
       

+ 14 - 0
src/modules/resource/http.ts

@@ -34,4 +34,18 @@ export const http = ResourceModule.http({
   deletePromotion(id: string) {
     return this.request(`/h5/delete/${id}`, { method: "POST" });
   },
+
+
+   //   comp
+   createComp(data: any) {
+    return this.request("/frame/create", { method: "POST", data });
+  },
+
+  updateComp(data: any) {
+    return this.request("/frame/update", { method: "POST", data });
+  },
+
+  deleteComp(id: string) {
+    return this.request(`/frame/delete/${id}`, { method: "POST" });
+  },
 });

+ 1 - 0
src/modules/resource/index.ts

@@ -8,6 +8,7 @@ import { http } from "./http";
 import { store } from "./store";
 import { BusController } from "@/controllers/natsController";
 import { TreeController } from "@/controllers/queentreeController";
+import { ComponentController } from "./controllers/ComponentController";
 
 export class ResourceModule extends ModuleRoot {
   config = this.setConfig({

+ 3 - 2
src/pages/editor/EditPage/index.tsx

@@ -1,7 +1,7 @@
 import { initEditor } from "@/modules/editor";
-import { initResource, useResource } from "@/modules/resource";
+import { EditorMode } from "@/modules/editor/typings";
+import { useResource } from "@/modules/resource";
 import { SelectOneImage } from "@/pages/website/Material2/modal";
-import { queenApi } from "queenjs";
 import { defineComponent } from "vue";
 
 export default defineComponent(() => {
@@ -9,6 +9,7 @@ export default defineComponent(() => {
   const resource = useResource();
 
   const params = new URLSearchParams(location.hash.split("?")[1]);
+  editor.actions.switchMode((params.get("mode") || "editPage") as EditorMode);
   editor.actions.initDesign(params.get("id") || "");
 
   editor.controls.pickCtrl.onPickImage = async (maxCount: number) => {

+ 86 - 0
src/pages/website/MyComps/components/CompItem.tsx

@@ -0,0 +1,86 @@
+import { css, cx } from "@linaria/core";
+import { IconMore } from "@queenjs/icons";
+import { Image, View } from "@queenjs/ui";
+import { Divider, Dropdown, Menu, Tag } from "ant-design-vue";
+import dayjs from "dayjs";
+import { defineUI } from "queenjs";
+import { any } from "vue-types";
+
+export default defineUI({
+  props: {
+    record: any(),
+  },
+  emits: ["edit", "preview", "menu"],
+  setup(props, {emit}) {
+    return () => {
+      const { record } = props;
+      return (
+        <div class={cx(itemStyles, "relative")}>
+          <View ratio={1.4} class="overflow-hidden relative">
+            <Image class="h-1/1 w-1/1" src={record?.thumbnail?.url} />
+            <Tag
+              color="#E88B00"
+              // color="rgba(0, 0, 0, 0.4)"
+              class="absolute top-0 left-0 z-1 !rounded-none"
+            >
+              未发布
+            </Tag>
+            <div class="absolute inset-0 flex items-center justify-center opacity-0 hover:opacity-100 transition-opacity">
+              <div
+                class="text-white icon_action w-60px leading-60px orange cursor-pointer rounded-1/2 text-center"
+                onClick={()=>emit("edit", props.record)}
+              >
+                编辑
+              </div>
+            </div>
+          </View>
+          <div class="item_footer rounded-b-4px flex items-center justify-between p-15px">
+            <div>
+              <div class="text-white text-bold">{record.title}</div>
+              <div class="flex items-center text-opacity-60 text-white text-12px mt-5px">
+                {dayjs(record.updateTime).format("YYYY.MM.DD")} 发布{" "}
+                <Divider type="vertical"></Divider>
+                0次浏览
+              </div>
+            </div>
+            <Dropdown
+              placement="bottom"
+              overlay={
+                <Menu class="w-90px">
+                  <Menu.Item>
+                    <div
+                      onClick={() => emit("menu", "rename")}
+                    >
+                      重命名
+                    </div>
+                  </Menu.Item>
+                  <Menu.Item>
+                    <div
+                      onClick={() => emit("menu", "delete")}
+                    >
+                      删除
+                    </div>
+                  </Menu.Item>
+                </Menu>
+              }
+            >
+              <IconMore class="text-22px cursor-pointer" />
+            </Dropdown>
+          </div>
+        </div>
+      );
+    };
+  },
+});
+
+const itemStyles = css`
+  .item_footer {
+    background: #414141;
+  }
+  .icon_action {
+    background-color: rgba(0, 0, 0, 0.5);
+    &.orange {
+      background-color: rgba(232, 139, 0, 0.5);
+    }
+  }
+`;

+ 18 - 0
src/pages/website/MyComps/components/Header.tsx

@@ -0,0 +1,18 @@
+
+import { Button } from "ant-design-vue";
+import { defineUI } from "queenjs";
+
+export default defineUI({
+  emits: ["add"],
+  setup(props, {emit}) {
+    
+    return () => (
+      <div class="flex items-center justify-between">
+        <h3 class="text-22px">我的组件</h3>
+        <Button type="primary" onClick={()=>emit("add")}>
+          新增+
+        </Button>
+      </div>
+    );
+  },
+});

+ 45 - 0
src/pages/website/MyComps/components/index.tsx

@@ -0,0 +1,45 @@
+import List from "@/components/AssetsList";
+import { ComponentController } from "@/modules/resource/controllers/ComponentController";
+import { defineUI } from "queenjs";
+import { onMounted } from "vue";
+import { any } from "vue-types";
+import CompItem from "./CompItem";
+import Header from "./Header";
+
+export default defineUI({
+  props: {
+    Controller: any<ComponentController>().isRequired,
+  },
+  slots: {
+    Header,
+    List,
+  },
+  setup(props, { slots }) {
+    onMounted(() => {
+      props.Controller.ListCtrl.loadPage(1);
+    });
+
+    return () => {
+      return (
+        <div>
+          <slots.Header onAdd={props.Controller.createComp} />
+          <slots.List
+            gap="25px"
+            class="my-30px"
+            columns={4}
+            control={props.Controller.ListCtrl}
+            item={(record: any) => (
+              <CompItem
+                record={record}
+                onMenu={(name) => {
+                  props.Controller.onMenuClick(name, record);
+                }}
+                onEdit={(record) => props.Controller.onEdit(record)}
+              />
+            )}
+          />
+        </div>
+      );
+    };
+  },
+});

+ 22 - 0
src/pages/website/MyComps/index.tsx

@@ -0,0 +1,22 @@
+import { useResource } from "@/modules/resource";
+import { defineComponent } from "vue";
+
+import PromotionUI from "./components";
+export default defineComponent({
+  setup() {
+    const resource = useResource();
+    const ctrl = resource.helper.createCompController();
+    return () => (
+      <PromotionUI
+        Controller={ctrl}
+        slots={
+          {
+            // MaterialItem: ()=>{
+            //   return <div>item</div>
+            // }
+          }
+        }
+      ></PromotionUI>
+    );
+  },
+});

+ 9 - 3
src/pages/website/components/layout/LeftContent.tsx

@@ -1,16 +1,16 @@
 import { css, cx } from "@linaria/core";
 import {
   IconCamera,
+  IconCube,
   IconDashboard,
-  IconDownload,
-  IconSettings,
+  IconDownload
 } from "@queenjs/icons";
 import { Avatar, Divider } from "ant-design-vue";
+import { defineUI } from "queenjs";
 import { defineComponent } from "vue";
 import { array, object } from "vue-types";
 import Logo from "./Logo";
 import { UserController } from "./UserController";
-import { defineUI } from "queenjs";
 
 export default defineUI({
   props: {
@@ -32,6 +32,12 @@ export default defineUI({
         icon: IconDashboard,
         // suffix: "7",
       },
+      {
+        link: "/workbench/myComps",
+        label: "我的组件",
+        icon: IconCube,
+        // suffix: "32",
+      },
       {
         link: "/workstage/material",
         label: "我的素材",

+ 5 - 0
src/pages/website/router.ts

@@ -32,6 +32,11 @@ const routes: Array<RouteRecordRaw> = [
         name: "promotion",
         component: () => import("./Promotion2"),
       },
+      {
+        path: "/workbench/myComps",
+        name: "myComps",
+        component: () => import("./MyComps"),
+      },
       {
         path: "/workstage/material",
         name: "material",