lianghongjie 1 year ago
parent
commit
df1f3951d2

BIN
src/assets/imgs/default.png


+ 9 - 0
src/modules/editor/actions.ts

@@ -1,5 +1,6 @@
 import { EditorModule } from ".";
 import { DesignTemp } from "./objects/DesignTemp";
+import { ICompKeys } from "./typings";
 
 export const actions = EditorModule.action({
   initData(tempData: DesignTemp) {
@@ -8,4 +9,12 @@ export const actions = EditorModule.action({
   switchEditMode(v: string) {
     this.store.setEditMode(v);
   },
+  addCompToDesign(compKey: ICompKeys) {
+    const designComp = this.store.insertDesignContent(compKey);
+    this.actions.pickCurrComp(designComp.id);
+  },
+  pickCurrComp(compId: string) {
+    if (compId === this.store.currCompId) return;
+    this.store.setCurrComp(compId);
+  },
 });

+ 6 - 4
src/modules/editor/components/CompUI/baseUI/Image.tsx

@@ -1,9 +1,11 @@
-import { useEditor } from "@/modules/editor";
 import { defineComponent } from "vue";
 import { string } from "vue-types";
-import View from "./View";
+import { useEditor } from "../../..";
+import { View } from "./View";
 
-export default defineComponent({
+const imgDef = require("@/assets/imgs/default.png");
+
+export const Image = defineComponent({
   props: {
     value: string(),
   },
@@ -18,7 +20,7 @@ export default defineComponent({
       <View>
         <img
           class="w-1/1 h-1/1"
-          src={props.value}
+          src={props.value || imgDef}
           onClick={store.editMode === "edit" ? changeVal : undefined}
         />
       </View>

+ 4 - 3
src/modules/editor/components/CompUI/baseUI/Text.tsx

@@ -1,12 +1,13 @@
 import { useEditor } from "@/modules/editor";
 import { defineComponent } from "vue";
 import { string } from "vue-types";
-import View from "./View";
+import { View } from "./View";
 
-export default defineComponent({
+export const Text = defineComponent({
   props: {
-    value: string(),
+    value: string().def("请输入文本"),
   },
+  emits: ["update:value"],
   setup(props) {
     const { store } = useEditor();
     return () => (

+ 3 - 3
src/modules/editor/components/CompUI/baseUI/Textarea.tsx

@@ -3,11 +3,11 @@ import Quill from "quill";
 import "quill/dist/quill.bubble.css";
 import { defineComponent, onMounted, ref, watchEffect } from "vue";
 import { string } from "vue-types";
-import View from "./View";
+import { View } from "./View";
 
-export default defineComponent({
+export const Textarea = defineComponent({
   props: {
-    value: string().def("{}"),
+    value: string().def(""),
   },
   emits: ["update:value"],
   setup(props, { emit }) {

+ 57 - 15
src/modules/editor/components/CompUI/baseUI/View.tsx

@@ -1,22 +1,64 @@
 import { useEditor } from "@/modules/editor";
 import { css } from "@linaria/core";
-import { defineComponent } from "vue";
+import { defineComponent, onMounted, reactive, ref } from "vue";
 
-export default defineComponent({
-  emits: ["click"],
-  setup(props, { emit, slots }) {
-    const { store } = useEditor();
-    return () => (
-      <div
-        class={[store.editMode === "edit" && editStyle]}
-        onClick={() => emit("click")}
-      >
-        {slots.default?.()}
-      </div>
-    );
+export const View = defineComponent({
+  setup(props, { slots }) {
+    const { store, actions } = useEditor();
+    const viewRef = ref<HTMLElement>();
+    const state = reactive({
+      compId: "",
+    });
+    onMounted(() => {
+      const compEl = viewRef.value;
+      state.compId = compEl?.getAttribute("data-id") || "";
+    });
+    return () => {
+      const isComp = state.compId;
+      const isEdit = store.editMode === "edit";
+      const isSelected = isEdit && store.currCompId === state.compId;
+
+      return (
+        <div
+          ref={viewRef}
+          class={
+            isEdit && [
+              isComp ? viewStyle : "view_inside",
+              isSelected && "view_selected",
+            ]
+          }
+          onClick={
+            state.compId ? () => actions.pickCurrComp(state.compId) : undefined
+          }
+        >
+          {slots.default?.()}
+        </div>
+      );
+    };
   },
 });
 
-const editStyle = css`
-  outline: 1px dashed @inf-primary-color;
+const viewStyle = css`
+  &:hover {
+    outline: 1px dashed @inf-primary-color;
+  }
+
+  > * {
+    pointer-events: none;
+  }
+
+  &.view_selected {
+    outline: 1px solid @inf-primary-color;
+
+    > * {
+      pointer-events: auto;
+    }
+    &::after {
+      display: none;
+    }
+    .view_inside:hover {
+      outline: 1px dashed @inf-primary-color;
+      outline-offset: -1px;
+    }
+  }
 `;

+ 8 - 5
src/modules/editor/components/CompUI/customUI/Card.tsx

@@ -1,15 +1,18 @@
 import { defineComponent } from "vue";
 import { any } from "vue-types";
-import Image from "../baseUI/Image";
-import Textarea from "../baseUI/Textarea";
-import View from "../baseUI/View";
+import { Image } from "../baseUI/Image";
+import { Textarea } from "../baseUI/Textarea";
+import { View } from "../baseUI/View";
 
-export default defineComponent({
+export const Card = defineComponent({
   props: {
     value: any<{
       title1: string;
       title2: string;
-    }>().isRequired,
+    }>().def({
+      title1: "",
+      title2: "",
+    }),
   },
   setup(props) {
     return () => (

+ 77 - 0
src/modules/editor/components/CompUI/customUI/CardDemo.tsx

@@ -0,0 +1,77 @@
+import { css } from "@linaria/core";
+import { defineComponent, reactive, watch } from "vue";
+import { any } from "vue-types";
+import { Image, Text, View } from "..";
+
+export const CardDemo = defineComponent({
+  props: {
+    value: any<{
+      cardColumns: number;
+      themeColor: string;
+      title: string;
+      desc: string;
+      list: { name: string; img: string; desc: string }[];
+    }>().isRequired,
+  },
+  setup(props) {
+    const state = reactive(props.value);
+
+    watch(
+      () => [state.cardColumns],
+      () => {
+        const { cardColumns, list } = state;
+        const offset = cardColumns - list.length;
+        if (offset > 0) {
+          Array.from({ length: offset }, () => {
+            list.push({ name: "name", img: "", desc: "xxx" });
+          });
+        } else {
+          list.splice(cardColumns, offset * -1);
+        }
+      }
+    );
+
+    return () => (
+      <View>
+        <Text v-model={[state.title, "value"]} />
+        <Text v-model={[state.desc, "value"]} />
+        <div class="flex space-x-16px">
+          {state.list.map((d, i) => (
+            <div class="w-0 flex-1 relative">
+              <Image
+                class={imgStyle}
+                style={{ borderColor: state.themeColor }}
+                v-model={[d.img, "value"]}
+              />
+              <div
+                class={numberStyle}
+                style={{ backgroundColor: state.themeColor }}
+              >
+                {(++i / 100).toString().split(".")[1]}
+              </div>
+              <Text class="mt-24px" v-model={[d.name, "value"]} />
+              <Text v-model={[d.desc, "value"]} />
+            </div>
+          ))}
+        </div>
+      </View>
+    );
+  },
+});
+
+const imgStyle = css`
+  border-bottom: 2px solid;
+`;
+
+const numberStyle = css`
+  position: absolute;
+  left: 50%;
+  border-radius: 50%;
+  width: 40px;
+  height: 40px;
+  text-align: center;
+  line-height: 40px;
+  box-sizing: content-box;
+  border: 3px solid #fff;
+  transform: translate(-50%, -50%);
+`;

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

@@ -1,11 +1,6 @@
-import Image from "./baseUI/Image";
-import Text from "./baseUI/Text";
-import Textarea from "./baseUI/Textarea";
-import Card from "./customUI/Card";
-
-export default {
-  Text,
-  Textarea,
-  Image,
-  Card,
-};
+export { Image } from "./baseUI/Image";
+export { Text } from "./baseUI/Text";
+export { View } from "./baseUI/View";
+export { Textarea } from "./baseUI/Textarea";
+export { Card } from "./customUI/Card";
+export { CardDemo } from "./customUI/CardDemo";

+ 0 - 7
src/modules/editor/components/Panels/UIPanel/index.tsx

@@ -1,7 +0,0 @@
-import { defineUI } from "queenjs";
-
-export default defineUI({
-  setup() {
-    return;
-  },
-});

+ 10 - 3
src/modules/editor/components/Viewport/Canvas/index.tsx

@@ -6,14 +6,21 @@ export default defineUI({
     const editor = useEditor();
     const { store, components } = editor;
     return () => (
-      <div class="h-1/1 text-center overflow-y-auto scrollbar">
+      <div class="h-1/1 text-center">
         <div
-          class="inline-block w-375px min-h-750px"
+          class="inline-block w-375px h-750px overflow-y-auto scrollbar"
           style={store.designData.pageStyle}
         >
           {store.designData.content.map((d) => {
             const Comp = components.CompUI[d.compKey];
-            return <Comp {...d.props} v-model={[d.props.value, "value"]} />;
+            return (
+              <Comp
+                data-id={d.id}
+                {...(d.props || {})}
+                style={d.style}
+                v-model={[d.value, "value"]}
+              />
+            );
           })}
         </div>
       </div>

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

@@ -0,0 +1,39 @@
+import { useEditor } from "@/modules/editor";
+import { ICompKeys } from "@/modules/editor/typings";
+import { Radio } from "ant-design-vue";
+import { defineUI } from "queenjs";
+
+export default defineUI({
+  setup() {
+    const editor = useEditor();
+    function addCompUI(compKey: ICompKeys) {
+      editor.actions.addCompToDesign(compKey);
+    }
+    return () => (
+      <div>
+        <div class="p-16px border-bottom !border-2px">资源中心</div>
+        <div class="m-16px">
+          <Radio.Group>
+            <Radio.Button>模板</Radio.Button>
+            <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={() => addCompUI(compKey as ICompKeys)}
+                  >
+                    {uiOpt.name}
+                  </div>
+                );
+              }
+            )}
+          </div>
+        </div>
+      </div>
+    );
+  },
+});

+ 47 - 0
src/modules/editor/components/Viewport/Slider/SliderRight.tsx

@@ -0,0 +1,47 @@
+import { useEditor } from "@/modules/editor";
+import { getOption } from "@/modules/editor/config/compUIOptions/create";
+import { Input } from "ant-design-vue";
+import { get, set } from "lodash";
+import { defineUI } from "queenjs";
+
+export default defineUI({
+  setup() {
+    const editor = useEditor();
+
+    return () => {
+      const currCompUIOpts = getOption(
+        editor.config.compUIOptions,
+        editor.store.currComp?.compKey
+      );
+      return (
+        <div>
+          <div class="p-16px border-bottom !border-2px">设置栏</div>
+          {currCompUIOpts && (
+            <div class="m-16px">
+              <div>当前组件</div>
+              <div class="py-16px space-y-10px">
+                {currCompUIOpts.valueOpts.map((d) => {
+                  return (
+                    <div class="">
+                      <div>{d.label}</div>
+                      <Input
+                        value={get(editor.store.currComp?.value, d.dataIndex)}
+                        onChange={(e) =>
+                          set(
+                            editor.store.currComp?.value,
+                            d.dataIndex,
+                            e.target.value
+                          )
+                        }
+                      />
+                    </div>
+                  );
+                })}
+              </div>
+            </div>
+          )}
+        </div>
+      );
+    };
+  },
+});

+ 7 - 6
src/modules/editor/components/Viewport/index.tsx

@@ -1,23 +1,24 @@
 import { defineUI } from "queenjs";
 import Canvas from "./Canvas";
 import Header from "./Header";
-import Slider from "./Slider";
+import SliderLeft from "./Slider/SliderLeft";
+import SliderRight from "./Slider/SliderRight";
 
 export default defineUI({
   slots: {
     Header,
-    SliderLeft: Slider,
-    SliderRight: Slider,
+    SliderLeft,
+    SliderRight,
     Canvas,
   },
   setup(props, { slots }) {
     return () => (
       <div class="flex flex-col h-1/1">
-        <slots.Header class="p-16px border-bottom bg-component" />
+        <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" />
+          <slots.SliderLeft class="w-300px bg-component border-right !border-2px" />
           <slots.Canvas class="flex-1 p-30px" />
-          <slots.SliderRight class="w-300px bg-component border-left" />
+          <slots.SliderRight class="w-300px bg-component border-left !border-2px" />
         </div>
       </div>
     );

+ 1 - 1
src/modules/editor/components/index.ts

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

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

@@ -0,0 +1,30 @@
+import { IComponent, ObjType } from "queenjs/typing";
+import components from "../../components";
+import { ICompKeys } from "../../typings";
+
+type UIOptions = {
+  name: string;
+  component?: IComponent;
+  valueOpts: { label: string; dataIndex: string; dataType: string }[];
+  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 };
+  };
+}

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

@@ -0,0 +1,57 @@
+import { createUIOptions } from "./create";
+
+export const compUIOptions = createUIOptions({
+  Text: {
+    name: "文字",
+    valueOpts: [
+      {
+        label: "文字内容",
+        dataIndex: "value",
+        dataType: "text",
+      },
+    ],
+  },
+
+  Image: {
+    name: "图片",
+    valueOpts: [
+      {
+        label: "图片链接",
+        dataIndex: "value",
+        dataType: "picture",
+      },
+    ],
+  },
+
+  CardDemo: {
+    name: "卡片",
+    valueOpts: [
+      {
+        label: "标题",
+        dataIndex: "title",
+        dataType: "input",
+      },
+      {
+        label: "卡片数量",
+        dataIndex: "cardColumns",
+        dataType: "number",
+      },
+      {
+        label: "主题颜色",
+        dataIndex: "themeColor",
+        dataType: "color",
+      },
+    ],
+    defaultValue: {
+      cardColumns: 3,
+      themeColor: "#ffc001",
+      title: "标题",
+      desc: "描述",
+      list: [
+        { name: "name", img: "", desc: "xxx" },
+        { name: "name", img: "", desc: "xxx" },
+        { name: "name", img: "", desc: "xxx" },
+      ],
+    },
+  },
+});

+ 5 - 0
src/modules/editor/config/index.ts

@@ -0,0 +1,5 @@
+import { compUIOptions } from "./compUIOptions";
+
+export default {
+  compUIOptions,
+};

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

@@ -1,9 +1,12 @@
 import { ModuleRoot } from "queenjs";
 import { actions } from "./actions";
 import components from "./components";
+import config from "./config";
 import { store } from "./store";
 
 export class EditorModule extends ModuleRoot {
+  config = this.setConfig(config);
+
   actions = this.createActions(actions);
   store = this.createStore(store);
 

+ 6 - 4
src/modules/editor/objects/DesignTemp/DesignComp.ts

@@ -1,12 +1,14 @@
 import { nanoid } from "nanoid";
-import components from "../../components";
+import { ICompKeys } from "../../typings";
 
 export class DesignComp {
   id = nanoid();
-  compKey: keyof typeof components.CompUI = "Text";
-  props = {
-    value: undefined as any
+  compKey: ICompKeys = "Text";
+  style = {
+    margin: "12px",
   };
+  value: any = undefined;
+  props?: any;
 
   constructor(data?: Partial<DesignComp>) {
     if (!data) return;

+ 24 - 0
src/modules/editor/store.ts

@@ -1,11 +1,23 @@
 import { EditorModule } from ".";
+import { getOption } from "./config/compUIOptions/create";
 import { DesignTemp } from "./objects/DesignTemp";
+import { DesignComp } from "./objects/DesignTemp/DesignComp";
+import { ICompKeys } from "./typings";
 
 export const store = EditorModule.store({
   state: () => ({
     editMode: "edit",
+    currCompId: "",
     designData: new DesignTemp(),
   }),
+  getters: {
+    currComp(state) {
+      const comp = state.designData.content.find(
+        (d) => d.id === state.currCompId
+      );
+      return comp;
+    },
+  },
   actions: {
     setEditMode(v: string) {
       this.store.editMode = v;
@@ -13,5 +25,17 @@ export const store = EditorModule.store({
     initDesignData(data: Partial<DesignTemp>) {
       this.store.designData = new DesignTemp(data);
     },
+    insertDesignContent(compKey: ICompKeys, index?: number) {
+      index || (index = this.store.designData.content.length);
+      const comp = new DesignComp({
+        compKey,
+        value: getOption(this.config.compUIOptions, compKey)?.defaultValue,
+      });
+      this.store.designData.content.splice(index, 0, comp);
+      return comp;
+    },
+    setCurrComp(compId: string) {
+      this.store.currCompId = compId;
+    },
   },
 });

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

@@ -0,0 +1,3 @@
+import components from "./components";
+
+export type ICompKeys = keyof typeof components.CompUI;

+ 12 - 10
src/pages/editor/EditPage/index.tsx

@@ -1,4 +1,5 @@
 import { initEditor } from "@/modules/editor";
+import { DesignComp } from "@/modules/editor/objects/DesignTemp/DesignComp";
 import { defineComponent } from "vue";
 
 export default defineComponent(() => {
@@ -8,16 +9,17 @@ export default defineComponent(() => {
     pageStyle: {
       backgroundColor: "#fff",
     },
-    content: Array.from({ length: 1 }, () => ({
-      id: Math.random().toString(),
-      compKey: "Card",
-      props: {
-        value: {
-          title1: "xxd324xxx2",
-          title2: "",
-        },
-      },
-    })),
+    content: Array.from(
+      { length: 1 },
+      () =>
+        new DesignComp({
+          compKey: "Card",
+          value: {
+            title1: "",
+            title2: "",
+          },
+        })
+    ),
   });
 
   return () => <editor.components.Viewport class="!h-100vh" />;

+ 2 - 2
src/styles/theme-antd.js

@@ -2,10 +2,10 @@ const { getThemeVariables } = require("ant-design-vue/dist/theme");
 
 module.exports = Object.assign(
   getThemeVariables({
-    dark: false,
+    dark: true,
   }),
   {
-    "primary-color": "#4095EA",
+    "primary-color": "#E88B00",
     "component-background": "#ffffff",
     "tree-title-height": "32px",
   }

+ 13 - 12
src/styles/theme.less

@@ -1,29 +1,30 @@
-@import "@queenjs/theme/less/light";
+@import "@queenjs/theme/less/dark";
 
-@inf-primary-color: #6274db;
+@inf-primary-color: #e88b00;
 @inf-primary-hover-color: darken(@inf-primary-color, 5%);
 @inf-primary-active-color: saturate(darken(@inf-primary-color, 5%), 20%);
 @inf-primary-fade-color: fade(@inf-primary-color, 10%);
 
-@inf-text-color: #333333;
+@inf-text-color: #a9abaf;
 @inf-text-active-color: darken(@inf-text-color, 10%);
 @inf-text-passive-color: lighten(@inf-text-color, 10%);
 @inf-text-error-fade-color: fade(@inf-text-error-color, 10%);
-@inf-text-sub-color: #666666;
-@inf-text-less-color: #999999;
+@inf-text-sub-color: #999;
+@inf-text-less-color: #666;
 
 @inf-mask-bg: fade(#000000, 50%);
 
-@inf-border-color: #eaeaea;
+@inf-border-color: #1f1f1f;
 
-@inf-layout-bg: #f7f7f7;
+@inf-layout-bg: #262626;
+@inf-component-bg: #262626;
 
-@inf-header-bg: #242736;
-@inf-header-color: #e6f2ff;
-@inf-header-active-color: #89a9ff;
+@inf-header-bg: #262626;
+@inf-header-color: #a9abaf;
+@inf-header-active-color: #e88b00;
 
-@inf-primary-bg: #eff4ff;
-@inf-primary-hover-bg: #d8e4ff;
+@inf-primary-bg: #fae8cc;
+@inf-primary-hover-bg: darken(@inf-primary-bg, 10%);
 
 @inf-header-height: 72px;
 @inf-input-padding-inline: 0;