Преглед на файлове

Merge branch 'master' of http://124.70.149.18:10880/lianghj/queenshow

liwei преди 1 година
родител
ревизия
9150e5acff
променени са 48 файла, в които са добавени 555 реда и са изтрити 287 реда
  1. 2 2
      src/hooks/initRemSize.ts
  2. 5 0
      src/modules/editor/actions/edit.ts
  3. 4 3
      src/modules/editor/actions/init.ts
  4. 1 2
      src/modules/editor/components/Canvas/index.tsx
  5. 2 22
      src/modules/editor/components/CompUI/basicUI/Image/component.tsx
  6. 2 0
      src/modules/editor/components/CompUI/basicUI/Image/index.ts
  7. 6 5
      src/modules/editor/components/CompUI/basicUI/Text/component.tsx
  8. 2 0
      src/modules/editor/components/CompUI/basicUI/Text/index.ts
  9. 11 5
      src/modules/editor/components/CompUI/basicUI/View.tsx
  10. 0 24
      src/modules/editor/components/CompUI/customUI/Card/index.tsx
  11. 9 9
      src/modules/editor/components/CompUI/customUI/Cards/Card1/component.tsx
  12. 5 3
      src/modules/editor/components/CompUI/customUI/Cards/Card1/index.tsx
  13. 53 0
      src/modules/editor/components/CompUI/customUI/Cover/component.tsx
  14. 3 7
      src/modules/editor/components/CompUI/customUI/Cover/index.ts
  15. 0 20
      src/modules/editor/components/CompUI/customUI/Cover/index.tsx
  16. 33 0
      src/modules/editor/components/CompUI/customUI/Titles/Title1/component.tsx
  17. 38 0
      src/modules/editor/components/CompUI/customUI/Titles/Title1/index.ts
  18. 66 0
      src/modules/editor/components/CompUI/defines/createAttrsForm.tsx
  19. 7 2
      src/modules/editor/components/CompUI/defines/formOpts/createColorOpts.ts
  20. 45 0
      src/modules/editor/components/CompUI/formItems/ImagePicker.tsx
  21. 0 4
      src/modules/editor/components/CompUI/forms.ts
  22. 5 6
      src/modules/editor/components/CompUI/index.ts
  23. 0 4
      src/modules/editor/components/CompUI/options.ts
  24. 5 5
      src/modules/editor/components/Viewport/Content/index.tsx
  25. 9 2
      src/modules/editor/components/Viewport/Header/index.tsx
  26. 13 15
      src/modules/editor/components/Viewport/Slider/SliderLeft.tsx
  27. 5 3
      src/modules/editor/components/Viewport/Slider/SliderRight.tsx
  28. 0 4
      src/modules/editor/components/index.ts
  29. 0 29
      src/modules/editor/config/compUIOptions/create.ts
  30. 0 4
      src/modules/editor/config/compUIOptions/index.ts
  31. 6 2
      src/modules/editor/config/index.ts
  32. 2 1
      src/modules/editor/defines/DesignTemp/DesignComp.ts
  33. 2 1
      src/modules/editor/defines/DesignTemp/index.ts
  34. 16 0
      src/modules/editor/https/index.ts
  35. 4 3
      src/modules/editor/index.ts
  36. 4 6
      src/modules/editor/stores/index.ts
  37. 5 3
      src/modules/editor/typings.ts
  38. 11 7
      src/modules/resource/controllers/PromotionController.ts
  39. 3 7
      src/pages/editor/EditPage/index.tsx
  40. 2 2
      src/pages/website/CreateMat/components/Header.tsx
  41. 77 0
      src/pages/website/CreateMat/components/OutputTemplateItem.tsx
  42. 40 30
      src/pages/website/CreateMat/components/OutputTemplateModal.tsx
  43. 6 20
      src/pages/website/Material2/components/MaterialItem.tsx
  44. 9 3
      src/pages/website/Material2/components/PreviewModal.tsx
  45. 6 5
      src/pages/website/Material2/components/SelectListItemModal.tsx
  46. 15 4
      src/pages/website/Material2/controller.tsx
  47. 1 1
      src/pages/website/Promotion2/components/PromotionItem.tsx
  48. 15 12
      src/pages/website/Promotion2/components/index.tsx

+ 2 - 2
src/hooks/initRemSize.ts

@@ -1,8 +1,8 @@
 export function initRemSize() {
   function setRem() {
     const clientWidth = document.documentElement.clientWidth;
-    const width = clientWidth > 750 ? 375 : clientWidth;
-    const fontSize = (width / 375) * 100;
+    const width = clientWidth > 750 ? 750 : clientWidth;
+    const fontSize = (width / 750) * 50;
     document.documentElement.style.fontSize = fontSize + "px";
   }
 

+ 5 - 0
src/modules/editor/actions/edit.ts

@@ -1,3 +1,4 @@
+import { toRaw } from "vue";
 import { EditorModule } from "..";
 import { ICompKeys } from "../typings";
 
@@ -23,4 +24,8 @@ export const editActions = EditorModule.action({
     if (selIndex === targetIndex) return;
     this.store.moveComp(selIndex, targetIndex);
   },
+  // 保存项目
+  saveDesign() {
+    this.https.saveDesign(this.store.designData);
+  },
 });

+ 4 - 3
src/modules/editor/actions/init.ts

@@ -1,3 +1,4 @@
+import { Exception } from "queenjs";
 import { EditorModule } from "..";
 import { DesignTemp } from "../defines/DesignTemp";
 import { editActions } from "./edit";
@@ -11,12 +12,12 @@ export const initActions = EditorModule.action({
     // createProxyEffect(this.store, (type, paths, value) => {
     //   historyCtrl.onChange(this.store, type, paths, value);
     // });
-   
   },
 
   // 初始化数据
-  initData(tempData: DesignTemp) {
-    this.store.initDesignData(tempData);
+  async initDesign(id: string) {
+    const ret = await this.https.getDesignDetail(id);
+    this.store.initDesignData(ret.result);
   },
   // 切换模式
   switchMode(v: string) {

+ 1 - 2
src/modules/editor/components/Canvas/index.tsx

@@ -1,5 +1,4 @@
 import { defineUI } from "queenjs";
-import components from "..";
 import { useEditor } from "../..";
 
 export default defineUI({
@@ -11,7 +10,7 @@ export default defineUI({
       return (
         <div>
           {content.map((d) => {
-            const Comp: any = components.CompUI[d.compKey];
+            const Comp: any = editor.config.compUI[d.compKey];
             return (
               <Comp
                 key={d.id}

+ 2 - 22
src/modules/editor/components/CompUI/basicUI/Image/index.tsx → src/modules/editor/components/CompUI/basicUI/Image/component.tsx

@@ -1,10 +1,9 @@
-import { css } from "@linaria/core";
+import { useEditor } from "@/modules/editor";
 import { queenApi } from "queenjs";
 import { string } from "vue-types";
-import { useEditor } from "../../../..";
 import { createUIComp } from "../../defines/createUIComp";
 
-export const Image = createUIComp({
+export const Component = createUIComp({
   props: {
     value: string().def(""),
   },
@@ -24,22 +23,3 @@ export const Image = createUIComp({
     );
   },
 });
-
-const btnStyle = css`
-  position: absolute;
-  display: none;
-  top: 50%;
-  left: 50%;
-  width: 0.6rem;
-  height: 0.6rem;
-  line-height: 0.6rem;
-  text-align: center;
-  border-radius: 50%;
-  background-color: rgba(0, 0, 0, 0.5);
-  transform: translate(-50%, -50%);
-  cursor: pointer;
-
-  &:hover {
-    display: block;
-  }
-`;

+ 2 - 0
src/modules/editor/components/CompUI/basicUI/Image/options.ts → src/modules/editor/components/CompUI/basicUI/Image/index.ts

@@ -2,6 +2,8 @@ import { Dict_Imgs } from "@/dict";
 import { createAttrsForm } from "../../defines/createAttrsForm";
 import { createOptions } from "../../defines/createOptions";
 
+export { Component } from "./component";
+
 export const options = createOptions({
   name: "图片",
   value: Dict_Imgs.Default,

+ 6 - 5
src/modules/editor/components/CompUI/basicUI/Text/index.tsx → src/modules/editor/components/CompUI/basicUI/Text/component.tsx

@@ -3,7 +3,7 @@ import { Alignment } from "@ckeditor/ckeditor5-alignment";
 import { Bold, Italic } from "@ckeditor/ckeditor5-basic-styles";
 import { InlineEditor } from "@ckeditor/ckeditor5-editor-inline";
 import { Essentials } from "@ckeditor/ckeditor5-essentials";
-import { FontFamily, FontSize } from "@ckeditor/ckeditor5-font";
+import { FontFamily, FontSize, FontColor } from "@ckeditor/ckeditor5-font";
 import { Link } from "@ckeditor/ckeditor5-link";
 import { Paragraph } from "@ckeditor/ckeditor5-paragraph";
 import { css } from "@linaria/core";
@@ -11,7 +11,7 @@ import { reactive } from "vue";
 import { string } from "vue-types";
 import { createUIComp } from "../../defines/createUIComp";
 
-export const Text = createUIComp({
+export const Component = createUIComp({
   props: {
     value: string().def(""),
   },
@@ -25,6 +25,7 @@ export const Text = createUIComp({
         Italic,
         Link,
         Paragraph,
+        FontColor,
         FontSize,
         FontFamily,
         Alignment,
@@ -37,14 +38,14 @@ export const Text = createUIComp({
           // "undo",
           // "redo",
           // "|",
-          // "fontFamily",
+          "fontColor",
           "fontsize",
           "bold",
           "italic",
           "|",
           "alignment",
-          "|",
-          "link",
+          // "|",
+          // "link",
         ],
       },
     };

+ 2 - 0
src/modules/editor/components/CompUI/basicUI/Text/options.ts → src/modules/editor/components/CompUI/basicUI/Text/index.ts

@@ -1,6 +1,8 @@
 import { createAttrsForm } from "../../defines/createAttrsForm";
 import { createOptions } from "../../defines/createOptions";
 
+export { Component } from "./component";
+
 export const options = createOptions({
   name: "文本",
   value: "请输入内容",

+ 11 - 5
src/modules/editor/components/CompUI/basicUI/View.tsx

@@ -1,5 +1,5 @@
 import { css } from "@linaria/core";
-import { omit } from "lodash";
+import { omit, upperFirst } from "lodash";
 import { defineComponent } from "vue";
 import { any, string } from "vue-types";
 import { useEditor } from "../../..";
@@ -34,11 +34,16 @@ export const View = defineComponent({
       const style: any = {};
       const { background, layout } = props;
       const [w, h] = (layout?.size as number[]) || [];
-      if (background?.image) {
-        style["backgroundImage"] = `url(${background.image})`;
-      } else if (background?.color) {
-        style["backgroundColor"] = background.color;
+      if (background) {
+        Object.entries(background).forEach(([key, value]) => {
+          if (key === "image") {
+            value = `url(${value})`;
+          }
+          style["background" + upperFirst(key)] = value;
+        });
+        console.log(style, background);
       }
+
       if (w) style["width"] = helper.designToNaturalSize(w);
       if (h) style["height"] = helper.designToNaturalSize(h);
       if (layout?.offsetX) {
@@ -93,6 +98,7 @@ const viewStyle = css`
     display: inline-block;
     width: 100%;
     height: 100%;
+    background: no-repeat center / cover;
 
     &:hover {
       outline: 1px dashed @inf-primary-color;

+ 0 - 24
src/modules/editor/components/CompUI/customUI/Card/index.tsx

@@ -1,24 +0,0 @@
-import { any } from "vue-types";
-import { Image } from "../../basicUI/Image";
-import { Text } from "../../basicUI/Text";
-import { createUIComp } from "../../defines/createUIComp";
-
-export const Card = createUIComp({
-  props: {
-    value: any<{
-      title1: string;
-      title2: string;
-    }>().def({
-      title1: "",
-      title2: "",
-    }),
-  },
-  setup(props) {
-    return () => (
-      <>
-        <Image v-model={[props.value.title1, "value"]} />
-        <Text v-model={[props.value.title2, "value"]} />
-      </>
-    );
-  },
-});

+ 9 - 9
src/modules/editor/components/CompUI/customUI/CardDemo/index.tsx → src/modules/editor/components/CompUI/customUI/Cards/Card1/component.tsx

@@ -2,11 +2,11 @@ import { Dict_Imgs } from "@/dict";
 import { css } from "@linaria/core";
 import { reactive, watch } from "vue";
 import { any } from "vue-types";
-import { Image, Text } from "../..";
-import { createUIComp } from "../../defines/createUIComp";
-import { options } from "./options";
+import { options } from ".";
+import { Image, Text } from "../../..";
+import { createUIComp } from "../../../defines/createUIComp";
 
-export const CardDemo = createUIComp({
+export const Component = createUIComp({
   props: {
     value: any<typeof options.value>().isRequired,
   },
@@ -30,12 +30,12 @@ export const CardDemo = createUIComp({
 
     return () => (
       <>
-        <Text v-model={[state.title, "value"]} />
-        <Text v-model={[state.desc, "value"]} />
+        <Text.Component v-model={[state.title, "value"]} />
+        <Text.Component v-model={[state.desc, "value"]} />
         <div class="flex space-x-16px">
           {state.list.map((d, i) => (
             <div class="w-0 flex-1 relative">
-              <Image
+              <Image.Component
                 class={imgStyle}
                 style={{ borderColor: state.themeColor }}
                 v-model={[d.img, "value"]}
@@ -46,8 +46,8 @@ export const CardDemo = createUIComp({
               >
                 {(++i / 100).toString().split(".")[1]}
               </div>
-              <Text class="mt-24px" v-model={[d.name, "value"]} />
-              <Text v-model={[d.desc, "value"]} />
+              <Text.Component class="mt-24px" v-model={[d.name, "value"]} />
+              <Text.Component v-model={[d.desc, "value"]} />
             </div>
           ))}
         </div>

+ 5 - 3
src/modules/editor/components/CompUI/customUI/CardDemo/options.ts → src/modules/editor/components/CompUI/customUI/Cards/Card1/index.tsx

@@ -1,7 +1,9 @@
 import { Dict_Imgs } from "@/dict";
-import { createAttrsForm } from "../../defines/createAttrsForm";
-import { createOptions } from "../../defines/createOptions";
-import { createColorOpts } from "../../defines/formOpts/createColorOpts";
+import { createAttrsForm } from "../../../defines/createAttrsForm";
+import { createOptions } from "../../../defines/createOptions";
+import { createColorOpts } from "../../../defines/formOpts/createColorOpts";
+
+export { Component } from "./component";
 
 export const options = createOptions({
   name: "卡片",

+ 53 - 0
src/modules/editor/components/CompUI/customUI/Cover/component.tsx

@@ -0,0 +1,53 @@
+import { css } from "@linaria/core";
+import { isPc } from "@queenjs/utils";
+import { onMounted, ref } from "vue";
+import { any } from "vue-types";
+import { options } from ".";
+import { Text } from "../..";
+import { createUIComp } from "../../defines/createUIComp";
+
+export const Component = createUIComp({
+  props: {
+    value: any<typeof options.value>().isRequired,
+  },
+  setup(props) {
+    const elRef = ref();
+
+    onMounted(() => {
+      elRef.value.style.height = isPc()
+        ? "12.8rem"
+        : document.documentElement.clientHeight + "px";
+    });
+
+    return () => {
+      return (
+        <div class={compStyle} ref={elRef}>
+          <Text.Component
+            class="title"
+            v-model={[props.value.title, "value"]}
+          />
+          <div class="arrow">↓</div>
+        </div>
+      );
+    };
+  },
+});
+
+const compStyle = css`
+  .title {
+    margin-top: 1.4rem;
+  }
+  .arrow {
+    position: absolute;
+    left: 50%;
+    bottom: 0.42rem;
+    width: 0.7rem;
+    height: 0.7rem;
+    line-height: 0.7rem;
+    text-align: center;
+    color: #fff;
+    border: 2px solid #fff;
+    border-radius: 50%;
+    transform: translateX(-50%);
+  }
+`;

+ 3 - 7
src/modules/editor/components/CompUI/customUI/Cover/options.ts → src/modules/editor/components/CompUI/customUI/Cover/index.ts

@@ -1,11 +1,12 @@
 import { createAttrsForm } from "../../defines/createAttrsForm";
 import { createOptions } from "../../defines/createOptions";
 
+export { Component } from "./component";
+
 export const options = createOptions({
   name: "封面",
   value: {
-    title: `<p style="text-align:center;">新科技反光面料 引领潮流新风尚</p>`,
-    desc: `<p style="text-align:center;">时尚 | 精致 | 百搭</p>`,
+    title: `<p style="text-align:center;"><span style="color:hsl(0,0%,100%);font-size:28px;">新科技反光面料</span></p><p style="text-align:center;"><span style="color:hsl(0,0%,100%);font-size:28px;">引领潮流新风尚</span></p><p style="text-align:center;">&nbsp;</p><p style="text-align:center;"><span style="color:hsl(0,0%,100%);font-size:16px;">时尚 | 精致 | 百搭</span></p>`,
   },
   background: {
     image:
@@ -19,9 +20,4 @@ export const Form = createAttrsForm([
     dataIndex: "value.title",
     component: "Input",
   },
-  {
-    label: "描述",
-    dataIndex: "value.desc",
-    component: "Input",
-  },
 ]);

+ 0 - 20
src/modules/editor/components/CompUI/customUI/Cover/index.tsx

@@ -1,20 +0,0 @@
-import { any } from "vue-types";
-import { Text } from "../../basicUI/Text";
-import { createUIComp } from "../../defines/createUIComp";
-import { options } from "./options";
-
-export const Cover = createUIComp({
-  props: {
-    value: any<typeof options.value>().isRequired,
-  },
-  setup(props) {
-    return () => {
-      return (
-        <>
-          <Text v-model={[props.value.title, "value"]} />
-          <Text v-model={[props.value.desc, "value"]} />
-        </>
-      );
-    };
-  },
-});

+ 33 - 0
src/modules/editor/components/CompUI/customUI/Titles/Title1/component.tsx

@@ -0,0 +1,33 @@
+import { any } from "vue-types";
+import { options } from ".";
+import { createUIComp } from "../../../defines/createUIComp";
+import { Text } from "../../..";
+import { css } from "@linaria/core";
+
+export const Component = createUIComp({
+  props: {
+    value: any<typeof options.value>().isRequired,
+  },
+  setup(props) {
+    return () => (
+      <Text.Component
+        style={{
+          "--theme-color": props.value.themeColor,
+        }}
+        class={[
+          compStyle,
+          props.value.themeType && `style_${props.value.themeType}`,
+        ]}
+        v-model={[props.value.title, "value"]}
+      />
+    );
+  },
+});
+
+const compStyle = css`
+  &.style_border {
+    border-top: 1px solid;
+    border-bottom: 1px solid;
+    border-color: var(--theme-color);
+  }
+`;

+ 38 - 0
src/modules/editor/components/CompUI/customUI/Titles/Title1/index.ts

@@ -0,0 +1,38 @@
+import { createAttrsForm } from "../../../defines/createAttrsForm";
+import { createOptions } from "../../../defines/createOptions";
+import { createColorOpts } from "../../../defines/formOpts/createColorOpts";
+
+export { Component } from "./component";
+
+export const options = createOptions({
+  name: "标题",
+  value: {
+    title: "我的风格我选择",
+    themeType: "none",
+    themeColor: "#666666",
+  },
+});
+
+export const Form = createAttrsForm([
+  {
+    label: "样式",
+    dataIndex: "value.themeType",
+    component: "Select",
+    props: {
+      options: [
+        { label: "无", value: "none" },
+        { label: "样式1", value: "border" },
+      ],
+    },
+  },
+  {
+    label: "主题颜色",
+    dataIndex: "value.themeColor",
+    ...createColorOpts(),
+  },
+  {
+    label: "标题",
+    dataIndex: "value.title",
+    component: "Input",
+  },
+]);

+ 66 - 0
src/modules/editor/components/CompUI/defines/createAttrsForm.tsx

@@ -5,6 +5,8 @@ import { defineComponent } from "vue";
 import { any } from "vue-types";
 import { GroupNumber } from "../formItems/GroupNumber";
 import { InputNumber } from "ant-design-vue";
+import { createColorOpts } from "./formOpts/createColorOpts";
+import { ImagePicker } from "../formItems/ImagePicker";
 
 const layoutColumns: ColumnItem[] = [
   {
@@ -58,6 +60,68 @@ const layoutColumns: ColumnItem[] = [
   },
 ];
 
+const bgColumns: ColumnItem[] = [
+  {
+    label: "背景颜色",
+    dataIndex: "background.color",
+    ...createColorOpts(),
+  },
+  {
+    label: "背景图片",
+    dataIndex: "background.image",
+    component: ImagePicker,
+  },
+  {
+    label: "repeat",
+    dataIndex: "background.repeat",
+    component: "Select",
+    props: {
+      options: [
+        "repeat",
+        "no-repeat",
+        "repeat-x",
+        "repeat-y",
+        "repeat-round",
+        "repeat-space",
+      ].map((v) => ({ label: v, value: v })),
+    },
+  },
+  {
+    label: "position",
+    dataIndex: "background.position",
+    component: "Select",
+    props: {
+      options: [
+        "center",
+        "top",
+        "bottom",
+        "left",
+        "left-top",
+        "left-bottom",
+        "right",
+        "right-top",
+        "right-bottom",
+      ].map((v) => ({ label: v, value: v })),
+    },
+  },
+  {
+    label: "size",
+    dataIndex: "background.size",
+    component: "Select",
+    props: {
+      options: ["auto", "cover", "contain"].map((v) => ({
+        label: v,
+        value: v,
+      })),
+    },
+  },
+  {
+    label: "clipPath",
+    dataIndex: "background.clipPath",
+    component: "Input",
+  },
+];
+
 export function createAttrsForm(valueColumns: ColumnItem[]) {
   return defineComponent({
     props: {
@@ -78,6 +142,8 @@ export function createAttrsForm(valueColumns: ColumnItem[]) {
               columns={layoutColumns}
               onChange={changeVal}
             />
+            <div>组件背景</div>
+            <FormUI data={component} columns={bgColumns} onChange={changeVal} />
             <div>组件属性</div>
             <FormUI
               data={component}

+ 7 - 2
src/modules/editor/components/CompUI/defines/formOpts/createColorOpts.ts

@@ -3,11 +3,16 @@ import { colorToHex, hexToColor } from "@queenjs/utils";
 
 export function createColorOpts(): Pick<
   ColumnItem,
-  "getValue" | "changeExtra"
+  "getValue" | "changeExtra" | "component"
 > {
   return {
+    component: "ColorPicker",
     getValue(v) {
-      return hexToColor(v);
+      try {
+        return hexToColor(v);
+      } catch (error) {
+        return [0, 0, 0];
+      }
     },
     changeExtra(data) {
       data.value = colorToHex(data.value);

+ 45 - 0
src/modules/editor/components/CompUI/formItems/ImagePicker.tsx

@@ -0,0 +1,45 @@
+import { css } from "@linaria/core";
+import { queenApi } from "queenjs";
+import { defineComponent } from "vue";
+import { string } from "vue-types";
+
+export const ImagePicker = defineComponent({
+  props: {
+    value: string(),
+  },
+  emits: ["change"],
+  setup(props, { emit }) {
+    async function changeVal() {
+      const [file] = await queenApi.selectFile({ accept: "image/*" });
+      emit("change", URL.createObjectURL(file));
+    }
+
+    return () => (
+      <div class={imageStyle} onClick={changeVal}>
+        {props.value ? <img src={props.value} /> : <i>+</i>}
+      </div>
+    );
+  },
+});
+
+const imageStyle = css`
+  width: 1.2rem;
+  height: 1.2rem;
+  padding: 0.12rem;
+  text-align: center;
+  line-height: 1.2rem;
+  border: 1px dashed @inf-text-less-color;
+  border-radius: 4px;
+  box-sizing: content-box;
+  cursor: pointer;
+
+  img {
+    width: 100%;
+    height: 100%;
+    object-fit: contain;
+  }
+  i {
+    font-style: unset;
+    font-size: 0.72rem;
+  }
+`;

+ 0 - 4
src/modules/editor/components/CompUI/forms.ts

@@ -1,4 +0,0 @@
-export { Form as Image } from "./basicUI/Image/options";
-export { Form as Text } from "./basicUI/Text/options";
-export { Form as CardDemo } from "./customUI/CardDemo/options";
-export { Form as Cover } from "./customUI/Cover/options";

+ 5 - 6
src/modules/editor/components/CompUI/index.ts

@@ -1,6 +1,5 @@
-export { Image } from "./basicUI/Image";
-export { Text } from "./basicUI/Text";
-export { Card } from "./customUI/Card";
-export { CardDemo } from "./customUI/CardDemo";
-export { Cover } from "./customUI/Cover";
-
+export * as Image from "./basicUI/Image";
+export * as Text from "./basicUI/Text";
+export * as Cover from "./customUI/Cover";
+export * as Card1 from "./customUI/Cards/Card1";
+export * as Title1 from "./customUI/Titles/Title1";

+ 0 - 4
src/modules/editor/components/CompUI/options.ts

@@ -1,4 +0,0 @@
-export { options as Image } from "./basicUI/Image/options";
-export { options as Text } from "./basicUI/Text/options";
-export { options as CardDemo } from "./customUI/CardDemo/options";
-export { options as Cover } from "./customUI/Cover/options";

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

@@ -5,11 +5,12 @@ import { Container, Draggable } from "vue-dndrop";
 import { useEditor } from "../../..";
 import { HotKeyCtrl } from "../../../controllers/HotKeyCtrl";
 import Canvas from "../../Canvas";
+import { AnyFun } from "queenjs/typing";
 
 export default defineUI({
   setup() {
     const editor = useEditor();
-    const { store, components, actions } = editor;
+    const { store, actions, config } = editor;
 
     const hotKeyCtrl = new HotKeyCtrl(editor);
     hotKeyCtrl.init();
@@ -27,9 +28,8 @@ export default defineUI({
             non-drag-area-selector={".drag-disable"}
           >
             {store.designData.content.map((d) => {
-              const Comp: any = components.CompUI[d.compKey];
-              console.log(d);
-              return (
+              const Comp: any = config.compUI[d.compKey]?.Component;
+              return Comp ? (
                 <Draggable key={d.id}>
                   <Comp
                     compId={d.id}
@@ -38,7 +38,7 @@ export default defineUI({
                     layout={d.layout}
                   />
                 </Draggable>
-              );
+              ) : undefined;
             })}
           </Container>
         ) : (

+ 9 - 2
src/modules/editor/components/Viewport/Header/index.tsx

@@ -1,12 +1,13 @@
 import { useEditor } from "@/modules/editor";
-import { Radio } from "ant-design-vue";
+import { Button, Radio } from "ant-design-vue";
 import { defineUI } from "queenjs";
 
 export default defineUI({
   setup() {
     const { store, actions } = useEditor();
     return () => (
-      <div class="text-center">
+      <div class="flex justify-between">
+        <aside></aside>
         <Radio.Group
           value={store.mode}
           onChange={(e) => actions.switchMode(e.target.value)}
@@ -14,6 +15,12 @@ export default defineUI({
           <Radio.Button value="edit">编辑</Radio.Button>
           <Radio.Button value="preview">预览</Radio.Button>
         </Radio.Group>
+
+        <aside>
+          <Button type="primary" onClick={() => actions.saveDesign()}>
+            保存
+          </Button>
+        </aside>
       </div>
     );
   },

+ 13 - 15
src/modules/editor/components/Viewport/Slider/SliderLeft.tsx

@@ -16,21 +16,19 @@ export default defineUI({
             <Radio.Button>组件</Radio.Button>
           </Radio.Group>
           <div class="py-16px space-y-10px">
-            {Object.entries(editor.config.compUIOptions).map(
-              ([compKey, uiOpt], i) => {
-                return (
-                  <div
-                    class="text-center leading-50px h-50px bg-dark-50 rounded"
-                    key={i}
-                    onClick={() =>
-                      editor.actions.addCompToDesign(compKey as ICompKeys)
-                    }
-                  >
-                    {uiOpt.name}
-                  </div>
-                );
-              }
-            )}
+            {Object.entries(editor.config.compUI).map(([compKey, uiOpt], i) => {
+              return (
+                <div
+                  class="text-center leading-50px h-50px bg-dark-50 rounded"
+                  key={i}
+                  onClick={() =>
+                    editor.actions.addCompToDesign(compKey as ICompKeys)
+                  }
+                >
+                  {uiOpt.options.name}
+                </div>
+              );
+            })}
           </div>
         </div>
       </div>

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

@@ -1,19 +1,21 @@
 import { useEditor } from "@/modules/editor";
 import { defineUI } from "queenjs";
+import { h } from "vue";
 
 export default defineUI({
   setup() {
     const editor = useEditor();
-    const { FormUI } = editor.components;
+    const { compUI } = editor.config;
 
     return () => {
       const { currComp } = editor.store;
-      const CompForm = FormUI[currComp?.compKey as "Image"];
+
       return (
         <div>
           <div class="p-16px border-bottom !border-2px">设置栏</div>
           <div class="m-16px">
-            {currComp && CompForm && <CompForm component={currComp} />}
+            {currComp?.compKey &&
+              h(compUI[currComp.compKey].Form, { component: currComp })}
           </div>
         </div>
       );

+ 0 - 4
src/modules/editor/components/index.ts

@@ -1,9 +1,5 @@
 import Viewport from "./Viewport";
-import * as CompUI from "./CompUI";
-import * as FormUI from "./CompUI/forms";
 
 export default {
   Viewport,
-  CompUI,
-  FormUI,
 };

+ 0 - 29
src/modules/editor/config/compUIOptions/create.ts

@@ -1,29 +0,0 @@
-import { IComponent, ObjType } from "queenjs/typing";
-import components from "../../components";
-import { ICompKeys } from "../../typings";
-
-type UIOptions = {
-  name: string;
-  component?: IComponent;
-  defaultValue?: any;
-};
-
-export function getOption<T extends ObjType<any>, K>(
-  obj: T,
-  key: K
-): K extends keyof T ? T[K] : undefined {
-  return (obj as any)[key];
-}
-
-export function createUIOptions<
-  T extends {
-    [name in ICompKeys]?: UIOptions;
-  }
->(opts: T) {
-  Object.entries(opts).forEach(([key, opt]) => {
-    opt.component = components.CompUI[key as ICompKeys];
-  });
-  return opts as {
-    [name in keyof T]: T[name] & { component: IComponent; defaultValue: any };
-  };
-}

+ 0 - 4
src/modules/editor/config/compUIOptions/index.ts

@@ -1,4 +0,0 @@
-import { createUIOptions } from "./create";
-import * as uiOptions from "../../components/CompUI/options";
-
-export const compUIOptions = createUIOptions(uiOptions);

+ 6 - 2
src/modules/editor/config/index.ts

@@ -1,5 +1,9 @@
-import { compUIOptions } from "./compUIOptions";
+import { Dict_Apis } from "@/dict";
+import * as compUI from "../components/CompUI";
 
 export default {
-  compUIOptions,
+  httpConfig: {
+    baseURL: Dict_Apis.promotion,
+  },
+  compUI,
 };

+ 2 - 1
src/modules/editor/defines/DesignTemp/DesignComp.ts

@@ -1,5 +1,6 @@
 import { nanoid } from "nanoid";
 import { Background, ICompKeys, Layout } from "../../typings";
+import { cloneDeep } from "lodash";
 
 export class DesignComp {
   id = nanoid();
@@ -10,6 +11,6 @@ export class DesignComp {
 
   constructor(data?: Partial<DesignComp>) {
     if (!data) return;
-    Object.assign(this, data);
+    Object.assign(this, cloneDeep(data));
   }
 }

+ 2 - 1
src/modules/editor/defines/DesignTemp/index.ts

@@ -1,10 +1,11 @@
 import { DesignComp } from "./DesignComp";
 
 export class DesignTemp {
+  _id!: string;
   title = "";
   pageStyle?: any;
   content: DesignComp[] = [];
-  
+
   constructor(data?: Partial<DesignTemp>) {
     if (!data) return;
     data.content = data.content?.map((d) => new DesignComp(d)) || [];

+ 16 - 0
src/modules/editor/https/index.ts

@@ -0,0 +1,16 @@
+import { EditorModule } from "..";
+import { DesignTemp } from "../defines/DesignTemp";
+
+export const https = EditorModule.http({
+  getDesignDetail(id: string) {
+    return this.request("/h5/detail/" + id, {
+      method: "GET",
+    });
+  },
+  saveDesign(data: DesignTemp) {
+    return this.request("/h5/update", {
+      method: "POST",
+      data,
+    });
+  },
+});

+ 4 - 3
src/modules/editor/index.ts

@@ -4,17 +4,18 @@ import { initActions } from "./actions/init";
 import components from "./components";
 import config from "./config";
 import { HistoryCtrl } from "./controllers/HistoryCtrl";
-import { store } from "./stores";
 import { helpers } from "./helpers";
-import { BusController } from "@/controllers/natsController";
+import { https } from "./https";
+import { store } from "./stores";
 
 export class EditorModule extends ModuleRoot {
   config = this.setConfig(config);
   components = this.useComponents(components);
 
   actions = this.createActions([initActions, editActions]);
+  https = this.createHttps(https);
   store = this.createStore(store);
-  helper = this.createHelper(helpers)
+  helper = this.createHelper(helpers);
 
   controls = {
     historyCtrl: new HistoryCtrl(this),

+ 4 - 6
src/modules/editor/stores/index.ts

@@ -1,6 +1,4 @@
-import { cloneDeep, omit } from "lodash";
 import { EditorModule } from "..";
-import { getOption } from "../config/compUIOptions/create";
 import { DesignTemp } from "../defines/DesignTemp";
 import { DesignComp } from "../defines/DesignTemp/DesignComp";
 import { ICompKeys } from "../typings";
@@ -31,13 +29,13 @@ export const store = EditorModule.store({
     },
     insertDesignContent(compKey: ICompKeys, index?: number) {
       index || (index = this.store.designData.content.length);
+      const options = this.config.compUI[compKey].options;
       const comp = new DesignComp({
         compKey,
-        ...cloneDeep(
-          omit(getOption(this.config.compUIOptions, compKey), ["name"])
-        ),
+        value: options?.value,
+        layout: options?.layout || {},
+        background: options?.background || {},
       });
-      console.log(comp);
       this.store.designData.content.splice(index, 0, comp);
       return comp;
     },

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

@@ -1,6 +1,6 @@
-import components from "./components";
+import * as CompUI from "./components/CompUI";
 
-export type ICompKeys = keyof typeof components.CompUI;
+export type ICompKeys = keyof typeof CompUI;
 
 export type Layout = {
   size?: number[]; // width height
@@ -13,6 +13,7 @@ export type Layout = {
 export type Background = {
   color?: string;
   image?: string;
+  imageStyle?: string;
   position?:
     | "bottom"
     | "center"
@@ -31,5 +32,6 @@ export type Background = {
     | "repeat-round"
     | "repeat-space";
   size?: "auto" | "cover" | "contain";
-  origin?: "border" | "padding" | "content";
+  clipPath?: string;
+  // origin?: "border" | "padding" | "content";
 };

+ 11 - 7
src/modules/resource/controllers/PromotionController.ts

@@ -1,11 +1,15 @@
 import { PageListController } from "@queenjs/controllers";
 
 export class PromotionController {
-    ListCtrl = new PageListController<any, any>();
-    createPromotion() {
-        console.log("createPromotion")
-    }
-    onMenuClick(menu:string, item:any) {
-        console.log("onMenuClick",menu, item)
-    }
+  ListCtrl = new PageListController<any, any>();
+  createPromotion() {
+    console.log("createPromotion");
+  }
+  onMenuClick(menu: string, item: any) {
+    console.log("onMenuClick", menu, item);
+  }
+  onEdit(item: any) {
+    const url = `${location.origin}/editor.html#/?id=${item._id}`;
+    location.href = url;
+  }
 }

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

@@ -3,13 +3,9 @@ import { defineComponent } from "vue";
 
 export default defineComponent(() => {
   const editor = initEditor();
-  editor.actions.initData({
-    title: "123",
-    pageStyle: {
-      backgroundColor: "#fff",
-    },
-    content: [],
-  });
+
+  const params = new URLSearchParams(location.hash.split("?")[1]);
+  editor.actions.initDesign(params.get("id") || "");
 
   return () => <editor.components.Viewport class="!h-100vh" />;
 });

+ 2 - 2
src/pages/website/CreateMat/components/Header.tsx

@@ -12,8 +12,8 @@ export default defineComponent({
 
     const showModal = () => {
       resource.showModal(<OutputTemplateModal />, {
-        title: "选择模板",
-        width: "900px",
+        title: "选择渲染模板",
+        width: "1000px",
       });
     };
 

+ 77 - 0
src/pages/website/CreateMat/components/OutputTemplateItem.tsx

@@ -0,0 +1,77 @@
+import { css, cx } from "@linaria/core";
+import { Image, View } from "@queenjs/ui";
+import { Checkbox, Radio, RadioChangeEvent } from "ant-design-vue";
+import { defineUI } from "queenjs";
+import { reactive } from "vue";
+import { any, bool } from "vue-types";
+
+const options = [
+  { label: "1倍", value: 1 },
+  { label: "2倍", value: 2 },
+];
+
+export default defineUI({
+  props: {
+    active: bool(),
+    record: any(),
+  },
+  emits: ["select", "change"],
+  setup(props, { emit }) {
+    const state = reactive({
+      qos: 1,
+    });
+
+    const change = (e: RadioChangeEvent) => {
+      state.qos = e.target.value;
+      emit("change", state.qos);
+    };
+
+    return () => {
+      const { active, record } = props;
+      //   console.log("record: ", record);
+      return (
+        <div>
+          <View
+            ratio={1.4}
+            onClick={() => emit("select")}
+            class={cx(
+              itemStyles,
+              "overflow-hidden",
+              "relative",
+              active && "active"
+            )}
+          >
+            {active && (
+              <Checkbox
+                checked
+                class="!-mt-0.2em absolute top-0 left-0 text-20px text-red-200 z-3"
+              />
+            )}
+            <Image class="h-1/1 w-1/1" src={record?.thumbnailUrl} />
+          </View>
+          <div>
+            <div class="py-8px">
+              尺寸:{record.width} * {record.height}
+            </div>
+            <div>
+              渲染分辨率:
+              <Radio.Group
+                disabled={!props.active}
+                value={state.qos}
+                options={options}
+                onChange={change}
+              />
+            </div>
+          </div>
+        </div>
+      );
+    };
+  },
+});
+
+const itemStyles = css`
+  border: 1px solid transparent;
+  &.active {
+    border-color: @inf-primary-color;
+  }
+`;

+ 40 - 30
src/pages/website/CreateMat/components/OutputTemplateModal.tsx

@@ -1,9 +1,9 @@
 import { useResource } from "@/modules/resource";
-import { List } from "@queenjs/ui";
 import { Button, Empty } from "ant-design-vue";
 import { queenApi, useModal } from "queenjs";
 import { defineComponent, reactive } from "vue";
 import { useRoute } from "vue-router";
+import OutputTemplateItem from "./OutputTemplateItem";
 
 export default defineComponent({
   setup() {
@@ -12,15 +12,19 @@ export default defineComponent({
     const route = useRoute();
 
     const state = reactive({
-      list: [] as {id: string, qos:1}[],
+      list: [] as { id: string; qos: 1 }[],
     }) as any;
 
-    const submit =async () => {
+    const submit = async () => {
       if (state.list.length == 0) {
         queenApi.messageError("请选择至少一个模板");
         return;
       }
-      const isOk = await resource.actions.submitRender(route.params.id as string, state.list, []);
+      const isOk = await resource.actions.submitRender(
+        route.params.id as string,
+        state.list,
+        []
+      );
       if (isOk) {
         modal.submit(true);
       }
@@ -29,33 +33,39 @@ export default defineComponent({
     return () => {
       return (
         <div>
-          <List data={resource.store.sourceDetail.images} columns={5}>
-            {{
-              item: (record: any) => {
-                return (
-                  <resource.components.MaterialItem
-                    record={record}
-                    class="cursor-pointer"
-                    active={ !!(state.list.find((v:any)=>v.id == record.id))}
-                    onSelect={() => {
-                      const selected =  !!(state.list.find((v:any)=>v.id == record.id))
-                      if (selected) {
-                        const index = state.list.findIndex(
-                          (d: any) => d.id == record.id
-                        );
-                        state.list.splice(index);
-                        return;
-                      }
-                      state.list.push({id: record.id, qos:1});
-                    }}
-                  />
-                );
-              },
-              empty: () => <Empty description="暂无数据" />,
-            }}
-          </List>
+          {resource.store.sourceDetail.images.length == 0 && (
+            <Empty description="暂无数据" />
+          )}
+          <div class="grid grid-cols-4 gap-10px">
+            {resource.store.sourceDetail.images.map((record: any) => (
+              <OutputTemplateItem
+                record={record}
+                class="cursor-pointer"
+                active={!!state.list.find((v: any) => v.id == record.id)}
+                onChange={(qos) => {
+                  const selected = state.list.find(
+                    (v: any) => v.id == record.id
+                  );
+                  selected.qos = qos;
+                }}
+                onSelect={() => {
+                  const selected = !!state.list.find(
+                    (v: any) => v.id == record.id
+                  );
+                  if (selected) {
+                    const index = state.list.findIndex(
+                      (d: any) => d.id == record.id
+                    );
+                    state.list.splice(index);
+                    return;
+                  }
+                  state.list.push({ id: record.id, qos: 1 });
+                }}
+              />
+            ))}
+          </div>
 
-          <div class="text-right">
+          <div class="text-right mt-10px">
             <Button onClick={modal.cancel}>取消</Button>
             <Button type="primary" class="ml-20px" onClick={submit}>
               确定

+ 6 - 20
src/pages/website/Material2/components/MaterialItem.tsx

@@ -1,9 +1,8 @@
 import { css, cx } from "@linaria/core";
 import { IconDelete } from "@queenjs/icons";
 import { Image, View } from "@queenjs/ui";
-import { Checkbox } from "ant-design-vue";
 import { defineUI } from "queenjs";
-import { any, bool, string } from "vue-types";
+import { any, string } from "vue-types";
 
 const renderStatus = {
   succ: "",
@@ -13,18 +12,17 @@ const renderStatus = {
 
 export default defineUI({
   props: {
-    active: bool().def(false),
     record: any(),
     use: string<"show" | "select" | "task">(),
   },
-  emits: ["delete", "select", "download", "use", "preview"],
+  emits: ["delete", "download", "use", "preview"],
   setup(props, { emit }) {
     return () => {
-      const { active, record, use } = props;
+      const { record, use } = props;
       // console.error("record: ", record);
 
       return (
-        <div class={cx(itemStyles, "relative", active && "active")}>
+        <div class={cx(itemStyles, "relative")}>
           <View ratio={1.4} class="overflow-hidden">
             {record.fileType == "video" ? (
               <video src={record.file?.url} class="h-1/1 w-1/1" />
@@ -36,22 +34,14 @@ export default defineUI({
                 }
               />
             )}
-            {active && (
-              <Checkbox
-                checked
-                class="!-mt-0.2em absolute top-0 left-0 text-20px text-red-200 z-3"
-              />
-            )}
+
             {use == "task" && record.status !== "succ" && (
               <div class="waiting absolute inset-0 z-2 flex items-center justify-center text-white">
                 {(renderStatus as any)[record.status || "default"]}
               </div>
             )}
             {use !== "task" && (
-              <div
-                class="absolute inset-0 flex items-center justify-center z-2 opacity-0 hover:opacity-100 transition-all text-white"
-                onClick={() => emit("select")}
-              >
+              <div class="absolute inset-0 flex items-center justify-center z-2 opacity-0 hover:opacity-100 transition-all text-white">
                 {use == "show" && (
                   <IconDelete
                     class="icon_del absolute right-5px top-5px p-3px rounded-2px text-14px cursor-pointer"
@@ -98,10 +88,6 @@ export default defineUI({
 });
 
 const itemStyles = css`
-  border: 1px solid transparent;
-  &.active {
-    border-color: @inf-primary-color;
-  }
   .orange {
     background-color: rgba(232, 139, 0, 0.5);
     &:hover {

+ 9 - 3
src/pages/website/Material2/components/PreviewModal.tsx

@@ -1,10 +1,13 @@
 import { Image } from "@queenjs/ui";
 import { defineUI } from "queenjs";
-import { any } from "vue-types";
+import { object } from "vue-types";
 
 export default defineUI({
   props: {
-    data: any(),
+    data: object<{
+      url: string;
+      fileType: "image" | "video";
+    }>().isRequired,
   },
   setup(props) {
     return () => {
@@ -12,7 +15,10 @@ export default defineUI({
       return (
         <div class="text-center min-h-200px">
           {data.fileType == "image" && (
-            <Image size={720} src={data.file?.url} class="max-w-1/1" />
+            <Image size={720} src={data.url} class="max-w-1/1" />
+          )}
+          {data.fileType == "video" && (
+            <video controls src={data.url} class="max-w-1/1" />
           )}
         </div>
       );

+ 6 - 5
src/pages/website/Material2/components/SelectListItemModal.tsx

@@ -6,13 +6,13 @@ import { PageListController } from "@queenjs/controllers";
 
 export default defineUI({
   slots: {
-     Item
+    Item,
   },
   props: {
     ListCtrl: any<PageListController<any, any>>().isRequired,
   },
-
-  setup(props, {slots}) {
+  emits: ["preview"],
+  setup(props, { emit, slots }) {
     const modal = useModal();
     return () => {
       return (
@@ -23,9 +23,10 @@ export default defineUI({
               <slots.Item
                 use="select"
                 record={record}
-                onUse={()=>{
-                  modal.submit(record  )
+                onUse={() => {
+                  modal.submit(record);
                 }}
+                onPreview={() => emit("preview", record)}
               />
             )}
           />

+ 15 - 4
src/pages/website/Material2/controller.tsx

@@ -8,16 +8,24 @@ export default function createController(resource:any, isSelectModel:boolean, se
         const ctrl = resource.controls.matTempListCtrl;
         ctrl.state.query = type == "video" ? { hasVideo: true } : {}
         ctrl.loadPage(1);
-        const record  = await resource.showModal(<SelectListItemModal ListCtrl={ctrl}  />, {
+        const record  = await resource.showModal(<SelectListItemModal ListCtrl={ctrl} onPreview={(record) => {
+          showPreviewModal({
+            url:record.thumbnail,
+            fileType:type == "video" ? "video":'image'
+          })
+        }}  />, {
           title: `${type === "image" ? "图片" : "视频"}模板中心`,
           width: "1000px",
         });
         resource.actions.selectMaterial(record);
     };
 
-    const  showPreviewModal = (data: any) => {
+    const  showPreviewModal = (data: {
+      url: string;
+      fileType: "image" | "video";
+    }) => {
       resource.showModal(<PreviewModal data={data}  />, {
-        title: "素材预览",
+        title: "预览",
         width: "800px",
       }); 
   };
@@ -37,7 +45,10 @@ export default function createController(resource:any, isSelectModel:boolean, se
     };
     ctrl.onItemClick = function (name, record) {
       if (name == "delete") return actions.deleteMaterial(record);
-      else if (name == "preview") return showPreviewModal(record);
+      else if (name == "preview") return showPreviewModal({
+        url: record.file.url,
+        fileType: record.fileType,
+      });
       return actions.downloadMaterial(record);
     };
 

+ 1 - 1
src/pages/website/Promotion2/components/PromotionItem.tsx

@@ -29,7 +29,7 @@ export default defineUI({
             <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")}
+                onClick={()=>emit("edit", props.record)}
               >
                 编辑
               </div>

+ 15 - 12
src/pages/website/Promotion2/components/index.tsx

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