qinyan 1 năm trước cách đây
mục cha
commit
1db9420e48

+ 1 - 1
src/assets/icons/components/IconImage.tsx

@@ -1,3 +1,3 @@
 
 import { createIcon } from '@queenjs/icons';
-export const IconImage = createIcon(<svg viewBox="0 0 16 16"><g transform="translate(0 0.302)"><g transform="translate(1.771 1.772)"><path fill="none" stroke="#a9abaf" stroke-linecap="round" stroke-linejoin="round" d="M17.141,6H7.013A1.013,1.013,0,0,0,6,7.013V17.141a1.013,1.013,0,0,0,1.013,1.013H17.141a1.013,1.013,0,0,0,1.013-1.013V7.013A1.013,1.013,0,0,0,17.141,6Z" transform="translate(-6 -6)"/><path fill="none" stroke="#a9abaf" stroke-linecap="round" stroke-linejoin="round" d="M14.688,16.376A1.688,1.688,0,1,0,13,14.688,1.688,1.688,0,0,0,14.688,16.376Z" transform="translate(-10.637 -10.637)"/><path fill="none" stroke="#a9abaf" stroke-linecap="round" stroke-linejoin="round" d="M18.154,29.376,14.44,26l-3.376,3.039L8.7,27.013,6,29.039" transform="translate(-6 -19.248)"/></g><rect fill="none" opacity="0.2" width="16" height="16" transform="translate(0 -0.302)"/></g></svg>)
+export const IconImage = createIcon(<svg viewBox="0 0 16 16"><g transform="translate(0 0.302)"><g transform="translate(1.771 1.772)"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M17.141,6H7.013A1.013,1.013,0,0,0,6,7.013V17.141a1.013,1.013,0,0,0,1.013,1.013H17.141a1.013,1.013,0,0,0,1.013-1.013V7.013A1.013,1.013,0,0,0,17.141,6Z" transform="translate(-6 -6)"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M14.688,16.376A1.688,1.688,0,1,0,13,14.688,1.688,1.688,0,0,0,14.688,16.376Z" transform="translate(-10.637 -10.637)"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M18.154,29.376,14.44,26l-3.376,3.039L8.7,27.013,6,29.039" transform="translate(-6 -19.248)"/></g><rect fill="none" opacity="0.2" width="16" height="16" transform="translate(0 -0.302)"/></g></svg>)

+ 13 - 0
src/assets/icons/components/IconShape.tsx

@@ -0,0 +1,13 @@
+
+import { createIcon } from '@queenjs/icons';
+export const IconShape = createIcon(<svg viewBox="0 0 28 28">
+    <g transform="translate(-4498 -5210)">
+        <rect fill="none" width="28" height="28" transform="translate(4498 5210)" />
+        <g transform="translate(4497.25 5210.002)">
+            <path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="1.2px"
+                d="M12,18a7,7,0,1,1,7-7" />
+            <path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="1.2px" d="M31,18H18V31H31Z"
+                transform="translate(-6.5 -7)" />
+        </g>
+    </g>
+</svg>)

+ 15 - 0
src/assets/icons/components/IconTpl.tsx

@@ -0,0 +1,15 @@
+
+import { createIcon } from '@queenjs/icons';
+export const IconTpl = createIcon(<svg viewBox="0 0 28 28">
+    <g transform="translate(-4498 -5210)">
+        <rect fill="none" width="28" height="28" transform="translate(4498 5210)" />
+        <g transform="translate(4496 5208.055)">
+            <rect fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="1.2px" width="20" height="20" rx="2"
+                transform="translate(6 5.945)" />
+            <path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="1.2px" stroke-linecap="round"
+                d="M6,17H25.945" transform="translate(0 -4.906)" />
+            <path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="1.2px" stroke-linecap="round"
+                d="M17,30.851V17" transform="translate(-4.906 -4.906)" />
+        </g>
+    </g>
+</svg>)

+ 12 - 1
src/assets/icons/components/IconVideo.tsx

@@ -1,3 +1,14 @@
 
 import { createIcon } from '@queenjs/icons';
-export const IconVideo = createIcon(<svg viewBox="0 0 16 16"><g transform="translate(-292 -40)"><rect fill="none" opacity="0.128" width="16" height="16" transform="translate(292 40)"/><g transform="translate(288 35.707)"><rect fill="none" stroke="#a9abaf" stroke-linejoin="round" stroke-linecap="round" width="12" height="12" rx="1" transform="translate(6 6.293)"/><path fill="none" stroke="#a9abaf" stroke-linejoin="round" d="M18.5,18.845V16.206l2.285,1.319,2.285,1.319-2.285,1.319L18.5,21.484Z" transform="translate(-8.268 -6.75)"/></g></g></svg>)
+export const IconVideo = createIcon(<svg viewBox="0 0 16 16">
+    <g transform="translate(-292 -40)">
+        <rect fill="none" opacity="0.128" width="16" height="16" transform="translate(292 40)" />
+        <g transform="translate(288 35.707)">
+            <rect fill="none" stroke="currentColor" stroke-linejoin="round" stroke-linecap="round" width="12"
+                height="12" rx="1" transform="translate(6 6.293)" />
+            <path fill="none" stroke="currentColor" stroke-linejoin="round"
+                d="M18.5,18.845V16.206l2.285,1.319,2.285,1.319-2.285,1.319L18.5,21.484Z"
+                transform="translate(-8.268 -6.75)" />
+        </g>
+    </g>
+</svg>)

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

@@ -25,6 +25,8 @@ export * from "./components/IconResizeY";
 export * from "./components/IconRight";
 export * from "./components/IconRotate";
 export * from "./components/IconSave";
+export * from "./components/IconShape";
 export * from "./components/IconText";
+export * from "./components/IconTpl";
 export * from "./components/IconVideo";
 export * from "./components/IconWechat";

+ 13 - 0
src/assets/icons/svg/Tpl.svg

@@ -0,0 +1,13 @@
+<svg viewBox="0 0 28 28">
+    <g transform="translate(-4498 -5210)">
+        <rect fill="none" width="28" height="28" transform="translate(4498 5210)" />
+        <g transform="translate(4496 5208.055)">
+            <rect fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="1.2px" width="20" height="20" rx="2"
+                transform="translate(6 5.945)" />
+            <path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="1.2px" stroke-linecap="round"
+                d="M6,17H25.945" transform="translate(0 -4.906)" />
+            <path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="1.2px" stroke-linecap="round"
+                d="M17,30.851V17" transform="translate(-4.906 -4.906)" />
+        </g>
+    </g>
+</svg>

+ 1 - 1
src/assets/icons/svg/image.svg

@@ -1 +1 @@
-<svg viewBox="0 0 16 16"><g transform="translate(0 0.302)"><g transform="translate(1.771 1.772)"><path fill="none" stroke="#a9abaf" stroke-linecap="round" stroke-linejoin="round" d="M17.141,6H7.013A1.013,1.013,0,0,0,6,7.013V17.141a1.013,1.013,0,0,0,1.013,1.013H17.141a1.013,1.013,0,0,0,1.013-1.013V7.013A1.013,1.013,0,0,0,17.141,6Z" transform="translate(-6 -6)"/><path fill="none" stroke="#a9abaf" stroke-linecap="round" stroke-linejoin="round" d="M14.688,16.376A1.688,1.688,0,1,0,13,14.688,1.688,1.688,0,0,0,14.688,16.376Z" transform="translate(-10.637 -10.637)"/><path fill="none" stroke="#a9abaf" stroke-linecap="round" stroke-linejoin="round" d="M18.154,29.376,14.44,26l-3.376,3.039L8.7,27.013,6,29.039" transform="translate(-6 -19.248)"/></g><rect fill="none" opacity="0.2" width="16" height="16" transform="translate(0 -0.302)"/></g></svg>
+<svg viewBox="0 0 16 16"><g transform="translate(0 0.302)"><g transform="translate(1.771 1.772)"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M17.141,6H7.013A1.013,1.013,0,0,0,6,7.013V17.141a1.013,1.013,0,0,0,1.013,1.013H17.141a1.013,1.013,0,0,0,1.013-1.013V7.013A1.013,1.013,0,0,0,17.141,6Z" transform="translate(-6 -6)"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M14.688,16.376A1.688,1.688,0,1,0,13,14.688,1.688,1.688,0,0,0,14.688,16.376Z" transform="translate(-10.637 -10.637)"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M18.154,29.376,14.44,26l-3.376,3.039L8.7,27.013,6,29.039" transform="translate(-6 -19.248)"/></g><rect fill="none" opacity="0.2" width="16" height="16" transform="translate(0 -0.302)"/></g></svg>

+ 11 - 0
src/assets/icons/svg/shape.svg

@@ -0,0 +1,11 @@
+<svg viewBox="0 0 28 28">
+    <g transform="translate(-4498 -5210)">
+        <rect fill="none" width="28" height="28" transform="translate(4498 5210)" />
+        <g transform="translate(4497.25 5210.002)">
+            <path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="1.2px"
+                d="M12,18a7,7,0,1,1,7-7" />
+            <path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="1.2px" d="M31,18H18V31H31Z"
+                transform="translate(-6.5 -7)" />
+        </g>
+    </g>
+</svg>

+ 12 - 1
src/assets/icons/svg/video.svg

@@ -1 +1,12 @@
-<svg viewBox="0 0 16 16"><g transform="translate(-292 -40)"><rect fill="none" opacity="0.128" width="16" height="16" transform="translate(292 40)"/><g transform="translate(288 35.707)"><rect fill="none" stroke="#a9abaf" stroke-linejoin="round" stroke-linecap="round" width="12" height="12" rx="1" transform="translate(6 6.293)"/><path fill="none" stroke="#a9abaf" stroke-linejoin="round" d="M18.5,18.845V16.206l2.285,1.319,2.285,1.319-2.285,1.319L18.5,21.484Z" transform="translate(-8.268 -6.75)"/></g></g></svg>
+<svg viewBox="0 0 16 16">
+    <g transform="translate(-292 -40)">
+        <rect fill="none" opacity="0.128" width="16" height="16" transform="translate(292 40)" />
+        <g transform="translate(288 35.707)">
+            <rect fill="none" stroke="currentColor" stroke-linejoin="round" stroke-linecap="round" width="12"
+                height="12" rx="1" transform="translate(6 6.293)" />
+            <path fill="none" stroke="currentColor" stroke-linejoin="round"
+                d="M18.5,18.845V16.206l2.285,1.319,2.285,1.319-2.285,1.319L18.5,21.484Z"
+                transform="translate(-8.268 -6.75)" />
+        </g>
+    </g>
+</svg>

+ 5 - 3
src/modules/editor/assets/icons/3d.svg

@@ -1,6 +1,8 @@
 <svg xmlns="http://www.w3.org/2000/svg" width="18.801" height="20.734" viewBox="0 0 18.801 20.734">
   <g id="组_16368" data-name="组 16368" transform="translate(-5.3 -3.3)">
-    <path id="路径_112765" data-name="路径 112765" d="M14.7,23.334l8.7-4.35L14.7,4,6,18.984Z" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.4"/>
-    <path id="路径_112766" data-name="路径 112766" d="M24,23.334V4" transform="translate(-9.3)" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.4"/>
+    <path id="路径_112765" data-name="路径 112765" d="M14.7,23.334l8.7-4.35L14.7,4,6,18.984Z" fill="none" stroke="#a9abaf"
+      stroke-linecap="round" stroke-linejoin="round" stroke-width="1.4" />
+    <path id="路径_112766" data-name="路径 112766" d="M24,23.334V4" transform="translate(-9.3)" fill="none" stroke="#a9abaf"
+      stroke-linecap="round" stroke-linejoin="round" stroke-width="1.4" />
   </g>
-</svg>
+</svg>

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

@@ -7,7 +7,7 @@ export { Component } from "./component";
 
 export const options = {
   name: "3D",
-  thumbnail: require("@/modules/editor/assets/icons/cube.svg"),
+  thumbnail: require("@/modules/editor/assets/icons/3d.svg"),
 };
 
 export const { createComp, useCompData } = createCompHooks({

+ 26 - 2
src/modules/editor/components/Viewport/Slider/SliderLeft/CustomComps.tsx

@@ -1,11 +1,35 @@
+import { Container, Draggable } from "vue-dndrop";
+import { any, string } from "vue-types";
+
 import { useEditor } from "@/modules/editor";
 import { ICompKeys } from "@/modules/editor/typings";
 import { Image } from "@queenjs/ui";
+import { useReactive } from "@queenjs/use";
 import { defineUI } from "queenjs";
-import { Container, Draggable } from "vue-dndrop";
-import { any } from "vue-types";
 
 export default defineUI({
+  props: {
+    compType: string<"user" | "senior">(),
+  },
+  setup(props) {
+    const editor = useEditor();
+    const { compUICtrl } = editor.controls;
+
+    const state = useReactive(() => ({
+      currComps() {
+        return Array.from(compUICtrl.state.components.values()).filter(
+          (item) => props.compType === item.compType
+        );
+      },
+    }));
+
+    return () => {
+      return <Comp components={state.currComps} />;
+    };
+  },
+});
+
+const Comp = defineUI({
   props: {
     components: any<
       {

+ 0 - 66
src/modules/editor/components/Viewport/Slider/SliderLeft/Frames.tsx

@@ -1,66 +0,0 @@
-import { useEditor } from "@/modules/editor";
-import { Image, Loadmore } from "@queenjs/ui";
-import { defineUI } from "queenjs";
-import { Container, Draggable } from "vue-dndrop";
-import { any } from "vue-types";
-
-export default defineUI({
-  props: {
-    dataSource: any<
-      {
-        _id: string;
-        title: string;
-        thumbnail: string;
-      }[]
-    >().isRequired,
-  },
-  setup(props) {
-    const editor = useEditor();
-
-    return () => {
-      const { dataSource } = props;
-
-      return (
-        // <div class="flex flex-col overflow-hidden">
-        <Container
-          class="space-y-10px scrollbar"
-          behaviour="copy"
-          group-name="canvas"
-          animation-duration={0}
-          get-child-payload={(index: number) => {
-            return {
-              type: "tpl",
-              data: dataSource[index],
-            };
-          }}
-        >
-          {dataSource.map((item) => {
-            return (
-              <Draggable key={item._id}>
-                <div
-                  class="text-center leading-50px bg-dark-50 rounded draggable-item"
-                  key={item._id}
-                  title={item.title}
-                  onClick={() => editor.actions.clickFrameToDesign(item)}
-                >
-                  <Image
-                    class="w-full rounded pointer-events-none"
-                    src={item.thumbnail}
-                    size={240}
-                  />
-                </div>
-              </Draggable>
-            );
-          })}
-          {/* <Loadmore
-            class="mt-20px"
-            loading={editor.controls.frameControl.listCtrl.state.loading}
-            canLoad={editor.controls.frameControl.listCtrl.state.canLoadNext}
-            onChange={editor.controls.frameControl.listCtrl.loadNextPage}
-          /> */}
-        </Container>
-        // </div>
-      );
-    };
-  },
-});

+ 0 - 104
src/modules/editor/components/Viewport/Slider/SliderLeft/MySources.tsx

@@ -1,104 +0,0 @@
-import { useEditor } from "@/modules/editor";
-import { useResource } from "@/modules/resource";
-import { css } from "@linaria/core";
-import { Image, Loadmore } from "@queenjs/ui";
-import { Radio } from "ant-design-vue";
-import { defineComponent, reactive } from "vue";
-import { Container, Draggable } from "vue-dndrop";
-
-export const MySources = defineComponent({
-  setup() {
-    const editor = useEditor();
-    const resource = useResource();
-    const state = reactive({
-      sourceType: "Image" as "Image" | "Video",
-    });
-
-    function getCurrCtrl() {
-      return resource.controls[
-        state.sourceType === "Image" ? "matImageListCtrl" : "matVideoListCtrl"
-      ];
-    }
-
-    function switchSource(v: "Image" | "Video") {
-      state.sourceType = v;
-      const ctrl = getCurrCtrl();
-      ctrl.hasLimit = true;
-      ctrl.loadPage(1);
-    }
-
-    function clickToDesign(url: string) {
-      editor.actions.clickCompToDesign(state.sourceType, (comp) => {
-        comp.value.url = url;
-      });
-    }
-
-    switchSource("Image");
-
-    return () => {
-      const control = getCurrCtrl();
-      return (
-        <div class="space-y-20px -mt-10px flex flex-col overflow-hidden">
-          <Radio.Group
-            class={radioCls}
-            value={state.sourceType}
-            size="small"
-            onChange={(e) => switchSource(e.target.value)}
-          >
-            <Radio.Button value="Image">图片</Radio.Button>
-            <Radio.Button value="Video">视频</Radio.Button>
-          </Radio.Group>
-          <div class="flex-1 -mr-15px pr-15px overflow-x-hidden overflow-y-auto scrollbar">
-            <Container
-              class="grid grid-cols-2 gap-15px"
-              behaviour="copy"
-              group-name="canvas"
-              animation-duration={0}
-              get-child-payload={(index: number) => {
-                return {
-                  type: state.sourceType,
-                  data: control.state.list[index],
-                };
-              }}
-            >
-              {control.state.list.map((item: any) => (
-                <Draggable class="">
-                  <div
-                    class="draggable-item"
-                    style={{ aspectRatio: 1 }}
-                    onClick={() => clickToDesign(item.file.url)}
-                  >
-                    {item.fileType == "video" ? (
-                      <video src={item.file.url} class="w-full h-full object-cover" />
-                    ) : (
-                      <Image
-                        class="w-full h-full"
-                        src={item.file.url}
-                        size={240}
-                      />
-                    )}
-                  </div>
-                </Draggable>
-              ))}
-            </Container>
-            <Loadmore
-              class="mt-20px"
-              loading={control.state.loading}
-              canLoad={control.state.canLoadNext}
-              onChange={control.loadNextPage}
-            />
-          </div>
-        </div>
-      );
-    };
-  },
-});
-
-const radioCls = css`
-  display: flex;
-  > label {
-    flex: 1;
-    width: 0;
-    text-align: center;
-  }
-`;

+ 67 - 0
src/modules/editor/components/Viewport/Slider/SliderLeft/Shapes.tsx

@@ -0,0 +1,67 @@
+import { Container, Draggable } from "vue-dndrop";
+
+import { useEditor } from "@/modules/editor";
+import { useResource } from "@/modules/resource";
+import { Image, Loadmore } from "@queenjs/ui";
+import Empty from "@queenjs/ui/empty";
+import { defineUI } from "queenjs";
+
+export default defineUI({
+  setup() {
+    const editor = useEditor();
+    const resource = useResource();
+
+    resource.controls.sysSvgListCtrl.loadPage(1);
+
+    return () => {
+      const ctrl = resource.controls.sysSvgListCtrl;
+      const dataSource = ctrl.state.list;
+
+      return (
+        <div class="flex-1 overflow-x-hidden overflow-y-auto scrollbar">
+          <Container
+            class="grid grid-cols-2 gap-15px"
+            behaviour="copy"
+            group-name="canvas"
+            animation-duration={0}
+            get-child-payload={(index: number) => {
+              return {
+                type: "Image",
+                data: dataSource[index],
+              };
+            }}
+          >
+            {dataSource.map((item: any) => {
+              return (
+                <Draggable key={item._id}>
+                  <div
+                    class="text-center leading-50px bg-dark-50 rounded draggable-item"
+                    key={item._id}
+                    // title={item.title}
+                    onClick={() => editor.actions.clickFrameToDesign(item)}
+                  >
+                    <Image
+                      class="w-full rounded pointer-events-none"
+                      src={item.thumbnail}
+                      size={240}
+                    />
+                  </div>
+                </Draggable>
+              );
+            })}
+          </Container>
+          {dataSource.length == 0 ? (
+            <Empty />
+          ) : (
+            <Loadmore
+              class="mt-20px"
+              loading={ctrl.state.loading}
+              canLoad={ctrl.state.canLoadNext}
+              onChange={ctrl.loadNextPage}
+            />
+          )}
+        </div>
+      );
+    };
+  },
+});

+ 103 - 0
src/modules/editor/components/Viewport/Slider/SliderLeft/Sources.tsx

@@ -0,0 +1,103 @@
+import { defineComponent, onMounted, watch } from "vue";
+import { Container, Draggable } from "vue-dndrop";
+import { string } from "vue-types";
+
+import { useEditor } from "@/modules/editor";
+import { useResource } from "@/modules/resource";
+import { Image, Loadmore } from "@queenjs/ui";
+
+export const Sources = defineComponent({
+  props: {
+    sourceType: string<"Image" | "Video">().def("Image"),
+    sourceFrom: string<"system" | "user">().def("user"),
+  },
+  setup(props) {
+    const editor = useEditor();
+    const resource = useResource();
+
+    function getCurrCtrl() {
+      // sysImageListCtrl sysVideoListCtrl matImageListCtrl matVideoListCtrl
+      if (props.sourceFrom == "user")
+        return resource.controls[
+          props.sourceType === "Image" ? "matImageListCtrl" : "matVideoListCtrl"
+        ];
+      return resource.controls[
+        props.sourceType === "Image" ? "sysImageListCtrl" : "sysVideoListCtrl"
+      ];
+    }
+
+    function clickToDesign(url: string) {
+      editor.actions.clickCompToDesign(props.sourceType, (comp) => {
+        comp.value.url = url;
+      });
+    }
+
+    function getData() {
+      const ctrl = getCurrCtrl();
+      ctrl.hasLimit = true;
+      ctrl.loadPage(1);
+    }
+
+    onMounted(() => getData());
+
+    watch(
+      () => [props.sourceFrom, props.sourceType],
+      () => {
+        const ctrl = getCurrCtrl();
+        if (ctrl.state.list.length == 0) getData();
+      }
+    );
+
+    return () => {
+      const control = getCurrCtrl();
+      const dataSource = control.state.list;
+
+      return (
+        <div class="flex-1 overflow-x-hidden overflow-y-auto scrollbar">
+          <Container
+            class="grid grid-cols-2 gap-15px"
+            behaviour="copy"
+            group-name="canvas"
+            animation-duration={0}
+            get-child-payload={(index: number) => {
+              return {
+                type: props.sourceType,
+                data: dataSource[index],
+              };
+            }}
+          >
+            {dataSource.map((item: any) => (
+              <Draggable>
+                <div
+                  class="draggable-item"
+                  style={{ aspectRatio: 1 }}
+                  onClick={() => clickToDesign(item.file.url)}
+                >
+                  {item.fileType == "video" ? (
+                    // <video
+                    //   src={item.file.url}
+                    //   class="w-full h-full object-cover"
+                    // />
+                    <Image class="w-full h-full" src={item.thumbnail} />
+                  ) : (
+                    <Image
+                      class="w-full h-full"
+                      src={item.file.url}
+                      size={240}
+                    />
+                  )}
+                </div>
+              </Draggable>
+            ))}
+          </Container>
+          <Loadmore
+            class="mt-20px"
+            loading={control.state.loading}
+            canLoad={control.state.canLoadNext}
+            onChange={control.loadNextPage}
+          />
+        </div>
+      );
+    };
+  },
+});

+ 59 - 0
src/modules/editor/components/Viewport/Slider/SliderLeft/Templates.tsx

@@ -0,0 +1,59 @@
+import { useEditor } from "@/modules/editor";
+import { Image, Loadmore } from "@queenjs/ui";
+import { defineUI } from "queenjs";
+import { Container, Draggable } from "vue-dndrop";
+
+export default defineUI({
+  setup() {
+    const editor = useEditor();
+    const { frameControl } = editor.controls;
+
+    return () => {
+      const ctrl = frameControl.listCtrl;
+      const dataSource = ctrl.state.list;
+
+      return (
+        <div class="flex-1 overflow-x-hidden overflow-y-auto scrollbar">
+          <Container
+            class="space-y-10px"
+            behaviour="copy"
+            group-name="canvas"
+            animation-duration={0}
+            get-child-payload={(index: number) => {
+              return {
+                type: "tpl",
+                data: dataSource[index],
+              };
+            }}
+          >
+            {dataSource.map((item) => {
+              return (
+                <Draggable key={item._id}>
+                  <div
+                    class="text-center leading-50px bg-dark-50 rounded draggable-item"
+                    key={item._id}
+                    title={item.title}
+                    style={{ aspectRatio: 1 }}
+                    onClick={() => editor.actions.clickFrameToDesign(item)}
+                  >
+                    <Image
+                      class="w-full rounded pointer-events-none"
+                      src={item.thumbnail}
+                      size={240}
+                    />
+                  </div>
+                </Draggable>
+              );
+            })}
+          </Container>
+          <Loadmore
+            class="mt-20px"
+            loading={ctrl.state.loading}
+            canLoad={ctrl.state.canLoadNext}
+            onChange={ctrl.loadNextPage}
+          />
+        </div>
+      );
+    };
+  },
+});

+ 140 - 61
src/modules/editor/components/Viewport/Slider/SliderLeft/index.tsx

@@ -1,81 +1,160 @@
-import { useEditor } from "@/modules/editor";
-import { css } from "@linaria/core";
-import { useReactive } from "@queenjs/use";
+import { Button } from "ant-design-vue";
+import { computed, reactive } from "vue";
+
+import { IconImage, IconShape, IconTpl, IconVideo } from "@/assets/icons";
+import { css, cx } from "@linaria/core";
+import { IconCube } from "@queenjs/icons";
 import { defineUI } from "queenjs";
 import CustomComps from "./CustomComps";
-import Frames from "./Frames";
-import { MySources } from "./MySources";
+import Shapes from "./Shapes";
+import { Sources } from "./Sources";
+import Templates from "./Templates";
+
+const tabs = [
+  {
+    title: "图片",
+    icon: IconImage,
+    content: [
+      {
+        title: "平台",
+        component: Sources,
+        props: { sourceType: "Image", sourceFrom: "system" },
+      },
+      {
+        title: "我的",
+        component: Sources,
+        props: { sourceType: "Image", sourceFrom: "user" },
+      },
+    ],
+  },
+  {
+    title: "形状",
+    icon: IconShape,
+    component: Shapes,
+  },
+  {
+    title: "视频",
+    icon: IconVideo,
+    content: [
+      {
+        title: "平台",
+        component: Sources,
+        props: { sourceType: "Video", sourceFrom: "system" },
+      },
+      {
+        title: "我的",
+        component: Sources,
+        props: { sourceType: "Video", sourceFrom: "user" },
+      },
+    ],
+  },
+  {
+    title: "组件",
+    icon: IconCube,
+    content: [
+      {
+        title: "平台",
+        component: CustomComps,
+        props: { compType: "senior" },
+      },
+      {
+        title: "我的",
+        component: CustomComps,
+        props: { compType: "user" },
+      },
+    ],
+  },
+  {
+    title: "模板",
+    icon: IconTpl,
+    component: Templates,
+  },
+];
 
 export default defineUI({
   setup() {
-    const editor = useEditor();
-    const { compUICtrl, frameControl } = editor.controls;
-
-    const tabs = [
-      { label: "模板", value: "frame" },
-      { label: "平台组件", value: "senior" },
-      { label: "我的组件", value: "user" },
-      { label: "我的素材", value: "source" },
-    ];
+    // @ts-ignore
+    const state = reactive({
+      tabIndex: 0,
+      compIndexs: [0, 0, 0, 0, 0],
+      currentTab: computed(() => {
+        return tabs[state.tabIndex];
+      }),
+      currCompIndex: computed(() => {
+        return state.compIndexs[state.tabIndex];
+      }),
+    });
 
-    const state = useReactive(() => ({
-      currTabType: "frame",
-      basicComps() {
-        return ["Text", "Image", "Video", "Web3D"].map(
-          (key) => compUICtrl.state.components.get(key) as any
-        );
-      },
-      currComps() {
-        return Array.from(compUICtrl.state.components.values()).filter(
-          (item) => state.currTabType === item.compType
-        );
-      },
-    }));
+    return () => {
+      const { tabIndex, currentTab, currCompIndex } = state;
+      const currComp = currentTab.component
+        ? currentTab
+        : currentTab.content?.[state.currCompIndex];
 
-    return () => (
-      <div class="h-full flex flex-col">
-        <div class="p-16px border-bottom !border-2px">资源中心</div>
-        <div class="m-16px flex-1 flex flex-col h-0">
-          <div class={tabStyle}>
-            {tabs.map((item) => {
+      return (
+        <div class="h-full flex">
+          <div class="w-70px pt-16px border-right !border-2px">
+            {tabs.map((record, index) => {
               return (
-                <span
-                  class={[state.currTabType === item.value && "checked"]}
-                  onClick={() => (state.currTabType = item.value)}
+                <div
+                  key={index}
+                  class={cx(
+                    tabItem,
+                    "relative mt-10px py-8px text-center cursor-pointer text-light-50 transition",
+                    state.tabIndex == index && "active"
+                  )}
+                  onClick={() => (state.tabIndex = index)}
                 >
-                  {item.label}
-                </span>
+                  <record.icon class="text-24px" />
+                  <div class="text-center mt-2px">{record.title}</div>
+                </div>
               );
             })}
           </div>
-          {state.currTabType == "frame" && (
-            <Frames
-              class="flex-1 -mx-16px p-16px"
-              dataSource={frameControl.listCtrl.state.list}
-            />
-          )}
-          {(state.currTabType == "user" || state.currTabType == "senior") && (
-            <CustomComps
-              class="flex-1 -mx-16px p-16px"
-              components={state.currComps}
+          <div class="flex-1 h-1/1 overflow-hidden flex flex-col">
+            {currentTab.content && (
+              <div class="my-5px ml-10px space-x-10px">
+                {currentTab.content?.map((item: any, index: number) => (
+                  <Button
+                    key={index}
+                    type="text"
+                    class={cx(
+                      "transition",
+                      currCompIndex == index && "font-bold"
+                    )}
+                    onClick={() => (state.compIndexs[tabIndex] = index)}
+                  >
+                    {item?.title}
+                  </Button>
+                ))}
+              </div>
+            )}
+
+            <currComp.component
+              class="flex-1 h-1/1 px-16px mb-10px overflow-y-auto"
+              {...currComp.props}
             />
-          )}
-          {state.currTabType == "source" && (
-            <MySources class="flex-1 -mx-16px p-16px" />
-          )}
+          </div>
         </div>
-      </div>
-    );
+      );
+    };
   },
 });
 
-const tabStyle = css`
-  @apply text-16px my-16px space-x-10px;
-
-  span {
-    cursor: pointer;
-    &.checked {
-      font-weight: bold;
+const tabItem = css`
+  &:hover {
+    color: @inf-primary-color;
+  }
+  &.active {
+    color: @inf-primary-color;
+    &:before {
+      content: "";
+      position: absolute;
+      left: 0;
+      top: 0;
+      height: 100%;
+      width: 3px;
+      background-color: @inf-primary-color;
     }
   }
 `;

+ 81 - 0
src/modules/editor/components/Viewport/Slider/SliderLeft/index.v1.tsx

@@ -0,0 +1,81 @@
+import { useEditor } from "@/modules/editor";
+import { css } from "@linaria/core";
+import { useReactive } from "@queenjs/use";
+import { defineUI } from "queenjs";
+import CustomComps from "./CustomComps";
+import Frames from "./Templates";
+import { Sources } from "./Sources";
+
+export default defineUI({
+  setup() {
+    const editor = useEditor();
+    const { compUICtrl, frameControl } = editor.controls;
+
+    const tabs = [
+      { label: "模板", value: "frame" },
+      { label: "平台组件", value: "senior" },
+      { label: "我的组件", value: "user" },
+      { label: "我的素材", value: "source" },
+    ];
+
+    const state = useReactive(() => ({
+      currTabType: "frame",
+      basicComps() {
+        return ["Text", "Image", "Video", "Web3D"].map(
+          (key) => compUICtrl.state.components.get(key) as any
+        );
+      },
+      currComps() {
+        return Array.from(compUICtrl.state.components.values()).filter(
+          (item) => state.currTabType === item.compType
+        );
+      },
+    }));
+
+    return () => (
+      <div class="h-full flex flex-col">
+        <div class="p-16px border-bottom !border-2px">资源中心</div>
+        <div class="m-16px flex-1 flex flex-col h-0">
+          <div class={tabStyle}>
+            {tabs.map((item) => {
+              return (
+                <span
+                  class={[state.currTabType === item.value && "checked"]}
+                  onClick={() => (state.currTabType = item.value)}
+                >
+                  {item.label}
+                </span>
+              );
+            })}
+          </div>
+          {state.currTabType == "frame" && (
+            <Frames
+              class="flex-1 -mx-16px p-16px"
+              // dataSource={frameControl.listCtrl.state.list}
+            />
+          )}
+          {/* {(state.currTabType == "user" || state.currTabType == "senior") && (
+            <CustomComps
+              class="flex-1 -mx-16px p-16px"
+              components={state.currComps}
+            />
+          )} */}
+          {state.currTabType == "source" && (
+            <Sources class="flex-1 -mx-16px p-16px" />
+          )}
+        </div>
+      </div>
+    );
+  },
+});
+
+const tabStyle = css`
+  @apply text-16px my-16px space-x-10px;
+
+  span {
+    cursor: pointer;
+    &.checked {
+      font-weight: bold;
+    }
+  }
+`;

+ 1 - 1
src/modules/editor/components/Viewport/index.tsx

@@ -19,7 +19,7 @@ export default defineUI({
       <div class="flex flex-col h-1/1">
         <slots.Header class="p-16px bg-component border-bottom !border-2px" />
         <div class="flex flex-1 h-0">
-          <slots.SliderLeft class="w-300px bg-component border-right !border-2px" />
+          <slots.SliderLeft class="w-330px bg-component border-right !border-2px" />
           <div class="flex-1 relative flex flex-col">
             <slots.Toolbar />
             <slots.Content />

+ 2 - 1
src/modules/editor/controllers/FrameCtrl/index.ts

@@ -17,7 +17,8 @@ export class FrameControl extends ModuleControl<EditorModule> {
 
   private async initData() {
     this.listCtrl.setCrudPrefix("/sys/h5");
-    this.listCtrl.state.size = 10000;
+    this.listCtrl.state.size = 10;
+    this.listCtrl.hasLimit = true;
     await this.listCtrl.loadPage(1);
   }
 }

+ 17 - 6
src/modules/resource/http.ts

@@ -16,12 +16,11 @@ export const http = ResourceModule.http({
   queryTplsDetail(id: string) {
     return this.request(`/tpls/detail/${id}`, { method: "GET" });
   },
-  
+
   sourceGen(data) {
-    return this.request(`/sourceGen/create`, { method: "POST", data});
+    return this.request(`/sourceGen/create`, { method: "POST", data });
   },
 
-
   //   promotion
   createPromotion(data: any) {
     return this.request("/h5/create", { method: "POST", data });
@@ -35,9 +34,8 @@ export const http = ResourceModule.http({
     return this.request(`/h5/delete/${id}`, { method: "POST" });
   },
 
-
-   //   comp
-   createComp(data: any) {
+  //   comp
+  createComp(data: any) {
     return this.request("/frame/create", { method: "POST", data });
   },
 
@@ -49,3 +47,16 @@ export const http = ResourceModule.http({
     return this.request(`/frame/delete/${id}`, { method: "POST" });
   },
 });
+
+/**
+ * 接口 说明
+ * /sys/frame/list 平台组件列表
+ * /sys/source/list?query={"fileType":***} 平台资源列表 fileType:image | video | svg
+ * /sys/h5/list 平台模板列表
+ * /source/list 用户资源列表
+ * /frame/list 用户组件列表
+ *
+ * /h5/detail/{id}?isSys=true  模板详情
+ * 组件和模板detail接口,isSys=true为平台资源
+ * isSys字段根据用户profile中是否有roles=["system"]决定
+ */

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

@@ -37,8 +37,13 @@ export class ResourceModule extends ModuleRoot {
     materialImageListCtrl: new PageListController(this.config?.httpConfig),
     materialVideoListCtrl: new PageListController(this.config?.httpConfig),
 
+    // 用户资源
     matImageListCtrl: new PageListController(this.config?.httpConfig),
     matVideoListCtrl: new PageListController(this.config?.httpConfig),
+    // 平台资源
+    sysImageListCtrl: new PageListController(this.config?.httpConfig),
+    sysVideoListCtrl: new PageListController(this.config?.httpConfig),
+    sysSvgListCtrl: new PageListController(this.config?.httpConfig),
   };
   natsBus = new BusController();
   treeController = new TreeController(this.natsBus);
@@ -69,6 +74,18 @@ export class ResourceModule extends ModuleRoot {
     this.controls.matVideoListCtrl.state.size = 20;
     this.controls.matVideoListCtrl.state.query = { fileType: "video" };
 
+    this.controls.sysImageListCtrl.setCrudPrefix("/sys/source");
+    this.controls.sysImageListCtrl.state.size = 20;
+    this.controls.sysImageListCtrl.state.query = { fileType: "image" };
+
+    this.controls.sysVideoListCtrl.setCrudPrefix("/sys/source");
+    this.controls.sysVideoListCtrl.state.size = 20;
+    this.controls.sysVideoListCtrl.state.query = { fileType: "video" };
+
+    this.controls.sysSvgListCtrl.setCrudPrefix("/sys/source");
+    this.controls.sysSvgListCtrl.state.size = 20;
+    this.controls.sysSvgListCtrl.state.query = { fileType: "svg" };
+
     this.natsBus.init();
   }
 }