qinyan 1 year ago
parent
commit
7d8ee199c3

+ 5 - 0
src/modules/resource/actions.ts

@@ -0,0 +1,5 @@
+import { ResourceModule } from ".";
+
+export const actions = ResourceModule.action({
+  //
+});

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

@@ -0,0 +1,10 @@
+import { ModuleRoot } from "queenjs";
+import { actions } from "./actions";
+import { store } from "./store";
+
+export class ResourceModule extends ModuleRoot {
+  actions = this.createActions([actions]);
+  store = this.createStore([store]);
+}
+
+export const { useResource, initResource } = ResourceModule.hook("Resource");

+ 8 - 0
src/modules/resource/store.ts

@@ -0,0 +1,8 @@
+import { ResourceModule } from ".";
+
+export const store = ResourceModule.store({
+  state: () => ({
+    //
+  }),
+  actions: {},
+});

+ 5 - 0
src/pages/website/Design/index.tsx

@@ -0,0 +1,5 @@
+import { defineComponent } from "vue";
+
+export default defineComponent(() => {
+  return () => <div>我的推广</div>;
+});

+ 8 - 1
src/pages/website/Home/index.tsx

@@ -1,5 +1,12 @@
+import { Button } from "ant-design-vue";
 import { defineComponent } from "vue";
 
 export default defineComponent(() => {
-  return () => <div>home</div>;
+  return () => (
+    <div class="text-center pt-20px">
+      <router-link to="/workbench/design">
+        <Button type="primary">工作台</Button>
+      </router-link>
+    </div>
+  );
 });

+ 100 - 0
src/pages/website/Material/components/MaterialItem.tsx

@@ -0,0 +1,100 @@
+import { css } from "@linaria/core";
+import { IconDelete } from "@queenjs/icons";
+import { Image, View } from "@queenjs/ui";
+import { defineComponent } from "vue";
+import { any } from "vue-types";
+
+export default defineComponent({
+  props: {
+    record: any(),
+  },
+  emits: ["delete", "select", "download"],
+  setup(props, { emit }) {
+    return () => {
+      const { record } = props;
+      console.log("record: ", record);
+      return (
+        <div class={itemStyles}>
+          <div class="waiting flex items-center justify-center text-white">
+            生成中…
+          </div>
+          <IconDelete class="icon_del" onClick={() => emit("delete")} />
+          <div class="item_actions flex">
+            <div class="btn_circle" onClick={() => emit("download")}>
+              下载
+            </div>
+            <div class="btn_circle" onClick={() => emit("select")}>
+              使用
+            </div>
+          </div>
+          <View ratio={1.4} class="overflow-hidden">
+            <Image class="h-1/1 w-1/1" src="" />
+          </View>
+        </div>
+      );
+    };
+  },
+});
+
+const itemStyles = css`
+  position: relative;
+  z-index: 1;
+  &:hover {
+    .icon_del,
+    .btn_circle {
+      opacity: 1;
+    }
+  }
+  .icon_del,
+  .btn_circle {
+    opacity: 0;
+    transition: all 0.2s ease;
+  }
+
+  .item_actions,
+  .icon_del,
+  .waiting {
+    position: absolute;
+    z-index: 2;
+    color: #fff;
+  }
+  .item_actions {
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%, -50%);
+  }
+  .btn_circle {
+    border-radius: 50%;
+    line-height: 56px;
+    width: 56px;
+    text-align: center;
+    cursor: pointer;
+    background-color: rgba(0, 0, 0, 0.7);
+    &:hover {
+      background-color: rgba(0, 0, 0, 0.8);
+    }
+    &:not(:first-child) {
+      margin-left: 10px;
+    }
+  }
+  .icon_del {
+    right: 5px;
+    top: 5px;
+    border-radius: 2px;
+    padding: 3px;
+    font-size: 14px;
+    cursor: pointer;
+    background-color: rgba(0, 0, 0, 0.5);
+    &:hover {
+      background-color: rgba(0, 0, 0, 0.6);
+    }
+  }
+  .waiting {
+    display: none;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    background-color: rgba(0, 0, 0, 0.2);
+  }
+`;

+ 24 - 0
src/pages/website/Material/components/MaterialTemplateModal.tsx

@@ -0,0 +1,24 @@
+import { css } from "@linaria/core";
+import { useModal } from "queenjs";
+import { defineComponent } from "vue";
+import { string } from "vue-types";
+import AssetsList from "../../components/AssetsList";
+
+export default defineComponent({
+  props: {
+    type: string(),
+  },
+  setup() {
+    const modal = useModal();
+
+    return () => {
+      return (
+        <div class={rootStyles}>
+          <AssetsList />
+        </div>
+      );
+    };
+  },
+});
+
+const rootStyles = css``;

+ 91 - 0
src/pages/website/Material/index.tsx

@@ -0,0 +1,91 @@
+import { useResource } from "@/modules/resource";
+import { css, cx } from "@linaria/core";
+import { Button, Space } from "ant-design-vue";
+import { defineComponent, reactive } from "vue";
+import AssetsList from "../components/AssetsList";
+import MaterialTemplateModal from "./components/MaterialTemplateModal";
+
+export default defineComponent({
+  setup() {
+    const resource = useResource();
+
+    const state = reactive({
+      type: "video",
+    });
+
+    const showModal = (type: string) => {
+      resource.showModal(<MaterialTemplateModal type={type} />, {
+        title: `${type === "image" ? "图片" : "视频"}模板中心`,
+        width: "1000px",
+      });
+    };
+
+    const uploadMaterial = () => {
+      //
+    };
+
+    const materialType = [
+      { name: "视频", key: "video" },
+      { name: "图片", key: "image" },
+    ];
+    return () => {
+      return (
+        <div class={rootStyles}>
+          <div>
+            <h3 class="text-22px">我的素材</h3>
+          </div>
+          <div class="flex items-center justify-between mt-20px">
+            <div class="filter">
+              <Space size={60}>
+                {materialType.map((d) => (
+                  <span
+                    key={d.key}
+                    onClick={() => (state.type = d.key)}
+                    class={cx(
+                      state.type == d.key && "active",
+                      "cursor-pointer btn_tab"
+                    )}
+                  >
+                    {d.name}
+                  </span>
+                ))}
+              </Space>
+            </div>
+            <div>
+              <Button type="primary" ghost onClick={uploadMaterial}>
+                上传素材
+              </Button>
+              <Button
+                type="primary"
+                ghost
+                class="ml-25px"
+                onClick={() => showModal("image")}
+              >
+                生成图片
+              </Button>
+              <Button
+                type="primary"
+                ghost
+                class="ml-15px"
+                onClick={() => showModal("video")}
+              >
+                生成视频
+              </Button>
+            </div>
+          </div>
+          <AssetsList class="mt-30px" />
+        </div>
+      );
+    };
+  },
+});
+
+const rootStyles = css`
+  .btn_tab {
+    padding: 3px 5px;
+    &:hover,
+    &.active {
+      color: @inf-primary-color;
+    }
+  }
+`;

+ 5 - 0
src/pages/website/Settings/index.tsx

@@ -0,0 +1,5 @@
+import { defineComponent } from "vue";
+
+export default defineComponent(() => {
+  return () => <div>Settings</div>;
+});

+ 35 - 0
src/pages/website/components/AssetsList.tsx

@@ -0,0 +1,35 @@
+import { Pagination } from "ant-design-vue";
+import { defineComponent } from "vue";
+import { number, string } from "vue-types";
+import { List } from "@queenjs/ui";
+import MaterialItem from "../Material/components/MaterialItem";
+import { css } from "@linaria/core";
+
+export default defineComponent({
+  props: {
+    columns: number().def(5),
+    gap: string().def("15px"),
+  },
+  setup(props) {
+    return () => {
+      const { columns, gap } = props;
+      const data = new Array(20).fill({ id: "" });
+      return (
+        <div class={styles}>
+          <List data={data} columns={columns} gap={gap}>
+            {{
+              item: () => <MaterialItem />,
+            }}
+          </List>
+          <div class="text-center my-30px">
+            <Pagination size="default" />
+          </div>
+        </div>
+      );
+    };
+  },
+});
+
+const styles = css`
+  /*  */
+`;

+ 174 - 0
src/pages/website/components/layout/LeftContent.tsx

@@ -0,0 +1,174 @@
+import { css, cx } from "@linaria/core";
+import { useAuth } from "@queenjs-modules/auth";
+import {
+  IconCamera,
+  IconDashboard,
+  IconDownload,
+  IconSettings
+} from "@queenjs/icons";
+import { Avatar, Divider } from "ant-design-vue";
+import { defineComponent } from "vue";
+import { array, object } from "vue-types";
+
+export default defineComponent({
+  setup() {
+    const auth = useAuth();
+
+    const footerOptions: TextListProps[] = [
+      {
+        label: "退出",
+        icon: IconDownload,
+        onClick: auth.actions.logout,
+      },
+    ];
+    const menuOptions = [
+      {
+        link: "/workbench/design",
+        label: "我的推广",
+        icon: IconDashboard,
+        suffix: "7",
+      },
+      {
+        link: "/workstage/material",
+        label: "我的素材",
+        icon: IconCamera,
+        suffix: "32",
+      },
+      {
+        link: "/settings",
+        label: "设置",
+        icon: IconSettings,
+      },
+    ];
+    return () => {
+      const { userInfo } = auth.store;
+
+      return (
+        <div class={cx(rootStyles, "p-30px h-1/1 flex flex-col")}>
+          <div class="mt-20px">
+            <router-link to="/">
+              <img
+                class="w-150px"
+                src={require("@/assets/imgs/Logo.png")}
+                alt=""
+              />
+            </router-link>
+          </div>
+          <div class="my-50px flex items-center">
+            <Avatar size={76} src={userInfo.avatar} />
+            <div class="ml-20px flex-1">
+              <p class="mb-10px text-16px font-bold">{userInfo.name}</p>
+              <div class="text-12px text-gray">
+                {userInfo.desc || "这个人很懒,什么都没留下..."}
+              </div>
+            </div>
+          </div>
+          <router-link to="/">
+            <TextListItem
+              option={{
+                label: "资源中心",
+                suffix: <span class="icon_new">new</span>,
+                icon: (
+                  <img
+                    class="w-18px mr-10px"
+                    src={require("@/assets/imgs/img-source.png")}
+                    alt=""
+                  />
+                ),
+              }}
+            />
+          </router-link>
+          <Divider />
+          <div class="flex-1">
+            {menuOptions?.map((option, index) => (
+              <router-link to={option.link} key={index}>
+                <TextListItem key={index} option={option} />
+              </router-link>
+            ))}
+          </div>
+          <div class="mb-30px">
+            <TextList options={footerOptions} />
+          </div>
+        </div>
+      );
+    };
+  },
+});
+
+const rootStyles = css`
+  .icon_new {
+    border-radius: 14px;
+    padding: 5px 10px;
+    text-transform: uppercase;
+    font-size: 12px;
+    line-height: 1;
+    background: linear-gradient(90deg, #9788ff 0%, #7e6bff 100%);
+    color: #fff;
+  }
+  .router-link-exact-active {
+    background-color: #fff;
+    display: block;
+    > div {
+      color: @inf-text-color;
+    }
+  }
+`;
+
+type TextListProps = {
+  label?: string;
+  suffix?: any;
+  icon?: any;
+  link?: string;
+  onClick?: () => void;
+};
+
+const TextList = defineComponent({
+  props: {
+    options: array<TextListProps>(),
+  },
+  setup(props) {
+    return () => {
+      return (
+        <div>
+          {props.options?.map((option, index) => (
+            <TextListItem key={index} option={option} />
+          ))}
+        </div>
+      );
+    };
+  },
+});
+
+const TextListItem = defineComponent({
+  props: { option: object<TextListProps>() },
+  setup(props) {
+    return () => {
+      const { option = {} } = props;
+      return (
+        <div
+          class={cx("flex items-center justify-between", itemStyles)}
+          onClick={() => option.onClick?.()}
+        >
+          {option.icon && <option.icon class="mr-8px text-20px" />}
+          <div class="flex-1">{option.label}</div>
+          {option.suffix && typeof option.suffix == "string" ? (
+            <span>{option.suffix}</span>
+          ) : (
+            option.suffix
+          )}
+        </div>
+      );
+    };
+  },
+});
+
+const itemStyles = css`
+  margin-top: 10px;
+  border-radius: 4px;
+  padding: 10px 25px 10px 20px;
+  color: @inf-text-sub-color;
+  cursor: pointer;
+  &:hover {
+    background: #fff;
+  }
+`;

+ 27 - 0
src/pages/website/components/layout/ProfileLayout.tsx

@@ -0,0 +1,27 @@
+import { Layout } from "ant-design-vue";
+import { defineComponent } from "vue";
+import LeftContent from "./LeftContent";
+
+export default defineComponent({
+  setup() {
+    return () => {
+      return (
+        <Layout class="!h-100vh flex" style="background-color: #f7f7f7">
+          <Layout.Sider
+            class="!bg-transparent overflow-y-auto"
+            theme="light"
+            width="380px"
+          >
+            <LeftContent />
+          </Layout.Sider>
+          <Layout.Content
+            style="background-color: #fff"
+            class="flex-1 my-20px mr-25px rounded-20px p-50px overflow-y-auto"
+          >
+            <router-view></router-view>
+          </Layout.Content>
+        </Layout>
+      );
+    };
+  },
+});

+ 2 - 1
src/pages/website/index.ts

@@ -1,5 +1,6 @@
 import { startApp } from "@/App";
 import { initAuthDef } from "@/hooks/initAuthDef";
+import { initResource } from "@/modules/resource";
 import router from "./router";
 
-startApp(router, [initAuthDef]);
+startApp(router, [initAuthDef, initResource]);

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

@@ -1,4 +1,5 @@
 import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
+import ProfileLayout from "./components/layout/ProfileLayout";
 
 const routes: Array<RouteRecordRaw> = [
   {
@@ -9,6 +10,32 @@ const routes: Array<RouteRecordRaw> = [
     },
     component: () => import("./Home"),
   },
+  {
+    path: "/workbench",
+    name: "workbench",
+    meta: {
+      needAuth: true,
+    },
+    redirect: "/workbench/design",
+    component: ProfileLayout,
+    children: [
+      {
+        path: "/workbench/design",
+        name: "design",
+        component: () => import("./Design"),
+      },
+      {
+        path: "/workstage/material",
+        name: "material",
+        component: () => import("./Material"),
+      },
+      {
+        path: "/settings",
+        name: "settings",
+        component: () => import("./Settings"),
+      },
+    ],
+  },
   {
     path: "/login",
     name: "login",

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

@@ -5,8 +5,8 @@ module.exports = Object.assign(
     dark: false,
   }),
   {
-    "primary-color": "#6274DB",
+    "primary-color": "#4095EA",
     "component-background": "#ffffff",
-    'tree-title-height': "32px"
+    "tree-title-height": "32px",
   }
 );

+ 2 - 2
src/styles/theme.less

@@ -16,7 +16,7 @@
 
 @inf-border-color: #eaeaea;
 
-// @inf-layout-bg: #EEF0F4;
+@inf-layout-bg: #f7f7f7;
 
 @inf-header-bg: #242736;
 @inf-header-color: #e6f2ff;
@@ -26,4 +26,4 @@
 @inf-primary-hover-bg: #d8e4ff;
 
 @inf-header-height: 72px;
-@inf-input-padding-inline: 0;
+@inf-input-padding-inline: 0;

+ 13 - 0
windi.config.js

@@ -0,0 +1,13 @@
+import { defineConfig } from "@vue/cli-service";
+
+export default defineConfig({
+  theme: {
+    textColor: {
+      primary: "#333",
+      secondary: "#666",
+      gray: "#999",
+      white: "#fff",
+      danger: "#e3342f",
+    },
+  },
+});