Browse Source

添加逻辑与ui分离版本示例

liwei 1 year ago
parent
commit
22dd13d2fb

+ 2 - 2
src/components/AssetsList.tsx

@@ -1,10 +1,10 @@
 import { css } from "@linaria/core";
 import { List } from "@queenjs/ui";
 import { Empty, Pagination } from "ant-design-vue";
-import { defineComponent } from "vue";
+import { defineUI } from "queenjs";
 import { any, number, string } from "vue-types";
 
-export default defineComponent({
+export default defineUI({
   props: {
     columns: number().def(5),
     gap: string().def("15px"),

+ 0 - 96
src/pages/website/Material2/UI.tsx

@@ -1,96 +0,0 @@
-import { css, cx } from "@linaria/core";
-import { Button } from "ant-design-vue";
-import { defineComponent, onMounted } from "vue";
-import AssetsList from "../components/AssetsList";
-import { any } from "vue-types";
-import MaterialItem from "@/modules/resource/components/MaterialItem";
-import { SourceController } from "./SourceController";
-
-const TabNames = {
-    "video":"视频",
-    "image": "图片",
-    "task": "渲染任务"
-}
-const BtnNames = {
-    "video":"生成视频",
-    "image": "生成图片",
-    "upload": "上传素材"
-}
-
-
-export default defineComponent({
-    props: {
-        Controller: any<SourceController>().isRequired
-    },
-    setup(props) {
-        onMounted(() =>{
-           const ctrl = props.Controller.getCurrControl()
-           ctrl.loadPage(1);
-        });
-
-    return () => {
-      const state = props.Controller.state;
-      const control = props.Controller.getCurrControl()
-
-      return (
-        <div class={rootStyles}>
-          <h3 class="text-22px">我的素材</h3>
-          <div class="flex items-center justify-between mt-20px">
-            <div class="filter space-x-60px">
-              {state.tabs.map((d) => (
-                <span
-                  key={d}
-                  onClick={() => props.Controller.switchTab(d)}
-                  class={cx(
-                        state.currTab == d && "active",
-                       "cursor-pointer btn_tab"
-                    )}
-                >
-                  {TabNames[d as "video"]}
-                </span>
-              ))}
-            </div>
-            <div>
-              {
-                state.btns.map((name, index)=>
-                    <Button
-                        ghost
-                        key={name}
-                        type="primary"
-                        class={"primary" + (index > 0 ? " ml-25px" : "")}
-                        onClick={()=>props.Controller.onBtnClick(name)}
-                  >
-                     {BtnNames[name as "video"]}
-                  </Button>
-                )
-              }
-            </div>
-          </div>
-          <AssetsList
-            columns={6}
-            class="mt-30px"
-            control={control}
-            item={(record: any) => (
-              <MaterialItem
-                record={record}
-                use={ state.currTab == "task" ? "task" : "show"}
-                onDelete={() => props.Controller.onItemClick("delete", record)}
-                onDownload={() =>props.Controller.onItemClick("download", record)}
-              /> 
-            )}
-          />
-        </div>
-      );
-    };
-  },
-});
-
-const rootStyles = css`
-  .btn_tab {
-    padding: 3px 5px;
-    &:hover,
-    &.active {
-      color: @inf-primary-color;
-    }
-  }
-`;

+ 62 - 0
src/pages/website/Material2/components/Material.tsx

@@ -0,0 +1,62 @@
+import { defineUI } from "queenjs";
+import  Toolbar  from "./MaterialToolbar"
+import { css, cx } from "@linaria/core";
+import { Button } from "ant-design-vue";
+import AssetsList from "@/components/AssetsList";
+import { any } from "vue-types";
+import MaterialItem from "./MaterialItem";
+import {MaterialController } from "./MaterialController";
+import { onMounted } from "vue";
+
+
+export default defineUI({
+    props: {
+        Controller: any<MaterialController>().isRequired,
+    },
+    slots: {
+        Toolbar,
+        AssetsList,
+        MaterialItem,
+    },
+
+    setup(props, { slots }) {
+        onMounted(() => {
+             props.Controller.getCurrControl().loadPage(1);
+        });
+
+        return ()=>{
+            const state = props.Controller.state;
+            const control = props.Controller.getCurrControl()
+            
+            return (<div class={rootStyles}>
+            <h3 class="text-22px">我的素材</h3>
+            <slots.Toolbar Controller={props.Controller} />
+
+            <slots.AssetsList
+              columns={6}
+              class="mt-30px"
+              control={control}
+              item={(record: any) => (
+                <slots.MaterialItem
+                  record={record}
+                  use={ state.currTab == "task" ? "task" : "show"}
+                  onDelete={() => props.Controller.onItemClick("delete", record)}
+                  onDownload={() =>props.Controller.onItemClick("download", record)}
+                />
+              )}
+            />
+          </div>);
+        }
+    },
+
+})
+
+const rootStyles = css`
+  .btn_tab {
+    padding: 3px 5px;
+    &:hover,
+    &.active {
+      color: @inf-primary-color;
+    }
+  }
+`;

+ 12 - 1
src/pages/website/Material2/SourceController.ts → src/pages/website/Material2/components/MaterialController.ts

@@ -1,7 +1,7 @@
 import { PageListController } from "@queenjs/controllers";
 import { reactive } from "vue";
 
-export class SourceController  {
+export class MaterialController  {
     state = reactive({currTab: "image", tabs:["image", "video", "task"], btns:["upload", "image","video"] , uploadType:"Default" as "image"|"video"|"Default"});
     imageCtrl = new PageListController<any, any>();
     vidoeCtrl = new PageListController<any, any>();
@@ -28,3 +28,14 @@ export class SourceController  {
         console.log("onItemClick", name, record)
     }
 }
+
+export const TabNames = {
+     "video":"视频",
+    "image": "图片",
+    "task": "渲染任务"
+}
+export const BtnNames = {
+    "video":"生成视频",
+    "image": "生成图片",
+    "upload": "上传素材"
+}

+ 112 - 0
src/pages/website/Material2/components/MaterialItem.tsx

@@ -0,0 +1,112 @@
+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";
+
+export default defineUI({
+  props: {
+    active: bool().def(false),
+    record: any(),
+    use: string<"show" | "select" | "task">(),
+  },
+  emits: ["delete", "select", "download", "use"],
+  setup(props, { emit }) {
+    return () => {
+      const { active, record, use } = props;
+      // console.error("record: ", record);
+
+      return (
+        <div class={cx(itemStyles, "relative", active && "active")}>
+          <View ratio={1.4} class="overflow-hidden">
+            {record.fileType == "video" ? (
+              <video src={record.file?.url} class="h-1/1 w-1/1" />
+            ) : (
+              <Image
+                class="h-1/1 w-1/1"
+                src={
+                  record?.thumbnail || record?.thumbnailUrl || record.file?.url
+                }
+              />
+            )}
+            {active && (
+              <Checkbox
+                checked
+                class="!-mt-0.2em absolute top-0 left-0 text-20px text-red-200 z-3"
+              />
+            )}
+            {use == "task" && (
+              <div class="waiting absolute inset-0 z-2 flex items-center justify-center text-white">
+                渲染中…
+              </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")}
+              >
+                {use == "show" && (
+                  <IconDelete
+                    class="icon_del absolute right-5px top-5px p-3px rounded-2px text-14px cursor-pointer"
+                    onClick={() => emit("delete")}
+                  />
+                )}
+                {use == "show" && (
+                  <div
+                    class="btn_circle rounded-1/2 text-center w-56px leading-56px cursor-pointer"
+                    onClick={() => emit("download")}
+                  >
+                    下载
+                  </div>
+                )}
+                {use == "select" && (
+                  <div
+                    class="btn_circle  rounded-1/2 text-center w-56px leading-56px cursor-pointer"
+                    onClick={(e) => {
+                      e.stopPropagation();
+                      emit("use");
+                    }}
+                  >
+                    使用
+                  </div>
+                )}
+              </div>
+            )}
+          </View>
+          {record.name && (
+            <div class="py-8px px-10px" style={{ backgroundColor: "#262626" }}>
+              {record.name}
+            </div>
+          )}
+        </div>
+      );
+    };
+  },
+});
+
+const itemStyles = css`
+  border: 1px solid transparent;
+  &.active {
+    border-color: @inf-primary-color;
+  }
+
+  .btn_circle {
+    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 {
+    background-color: rgba(0, 0, 0, 0.5);
+    &:hover {
+      background-color: rgba(0, 0, 0, 0.6);
+    }
+  }
+  .waiting {
+    background-color: rgba(0, 0, 0, 0.3);
+  }
+`;

+ 68 - 0
src/pages/website/Material2/components/MaterialToolbar.tsx

@@ -0,0 +1,68 @@
+import { Button } from "ant-design-vue";
+import { defineComponent } from "vue";
+import { css, cx } from "@linaria/core";
+import { any } from "vue-types";
+import { BtnNames, MaterialController, TabNames } from "./MaterialController";
+import { defineUI } from "queenjs";
+
+const materialType = [
+  { name: "视频", key: "video" },
+  { name: "图片", key: "image" },
+  { name: "渲染任务", key: "task" },
+];
+
+export default defineUI({
+  
+  props: {
+    Controller: any<MaterialController>().isRequired,
+  },
+  setup(props, { emit }) {
+    return () => {
+      const state = props.Controller.state;
+
+      return (
+            <div class="flex items-center justify-between mt-20px">
+            <div class="filter space-x-60px">
+              {state.tabs.map((d) => (
+                <span
+                  key={d}
+                  onClick={() => props.Controller.switchTab(d)}
+                  class={cx(
+                        state.currTab == d && "active",
+                       "cursor-pointer btn_tab"
+                    )}
+                >
+                  {TabNames[d as "video"]}
+                </span>
+              ))}
+            </div>
+            <div>
+              {
+                state.btns.map((name, index)=>
+                    <Button
+                        ghost
+                        key={name}
+                        type="primary"
+                        class={"primary" + (index > 0 ? " ml-25px" : "")}
+                        onClick={()=>props.Controller.onBtnClick(name)}
+                  >
+                     {BtnNames[name as "video"]}
+                  </Button>
+                )
+              }
+            </div>
+          </div>
+      );
+    };
+  },
+});
+
+const rootStyles = css`
+  .btn_tab {
+    padding: 3px 5px;
+    &:hover,
+    &.active {
+      color: @inf-primary-color;
+    }
+  }
+`;

+ 36 - 0
src/pages/website/Material2/components/SelectListItemModal.tsx

@@ -0,0 +1,36 @@
+import AssetsList from "@/components/AssetsList";
+import { defineUI, useModal } from "queenjs";
+import { any } from "vue-types";
+import Item from "./MaterialItem";
+import { PageListController } from "@queenjs/controllers";
+
+export default defineUI({
+  slots: {
+     Item
+  },
+  props: {
+    ListCtrl: any<PageListController<any, any>>().isRequired,
+  },
+
+  setup(props, {slots}) {
+    const modal = useModal();
+    return () => {
+      return (
+        <div>
+          <AssetsList
+            control={props.ListCtrl}
+            item={(record: any) => (
+              <slots.Item
+                use="select"
+                record={record}
+                onUse={()=>{
+                  modal.submit(record  )
+                }}
+              />
+            )}
+          />
+        </div>
+      );
+    };
+  },
+});

+ 36 - 0
src/pages/website/Material2/controller.tsx

@@ -0,0 +1,36 @@
+import SelectListItemModal from "./components/SelectListItemModal";
+import { MaterialController } from "./components/MaterialController";
+
+export default function createController(resource:any, isSelectModel:boolean, selectType :string) {
+    const {controls, actions} = resource;
+    const  showModal = async (type: string) => {
+        const ctrl = resource.controls.matTempListCtrl;
+        ctrl.state.query = type == "video" ? { hasVideo: true } : {}
+        ctrl.loadPage(1);
+        const record  = await resource.showModal(<SelectListItemModal ListCtrl={ctrl}  />, {
+          title: `${type === "image" ? "图片" : "视频"}模板中心`,
+          width: "1000px",
+        });
+        resource.actions.selectMaterial(record);
+    };
+
+    const ctrl = new MaterialController();
+    ctrl.imageCtrl = controls.materialImageListCtrl;
+    ctrl.vidoeCtrl = controls.materialVideoListCtrl;
+    ctrl.taskCtrl = controls.renderTaskListCtrl;
+    ctrl.onBtnClick = async function (name: string) {
+      if (name == "upload") {
+        const uploaded = await resource.actions.uploadMaterial();
+        ctrl.switchTab(uploaded.fileType, false);
+        ctrl.getCurrControl().loadPage(1);
+        return;
+      }
+      showModal(name);
+    };
+    ctrl.onItemClick = function (name, record) {
+      if (name == "delete") return actions.deleteMaterial(record);
+      return actions.downloadMaterial(record);
+    };
+
+    return ctrl
+}

+ 14 - 33
src/pages/website/Material2/index.tsx

@@ -1,41 +1,22 @@
 import { useResource } from "@/modules/resource";
 import { defineComponent } from "vue";
-import MaterialTemplateModal from "../Material/components/MaterialTemplateModal";
-import MaterialUI from "./UI";
-import { SourceController }  from "./SourceController";
+import Material from "./components/Material";
+import createController from "./controller";
 
 export default defineComponent({
   setup() {
     const resource = useResource();
-    const { controls, actions } = resource;
-
-    const showModal = (type: string) => {
-      resource.showModal(<MaterialTemplateModal type={type} />, {
-        title: `${type === "image" ? "图片" : "视频"}模板中心`,
-        width: "1000px",
-      });
-    };
-    const ctrl = new SourceController();
-    ctrl.imageCtrl = controls.materialImageListCtrl;
-    ctrl.vidoeCtrl = controls.materialVideoListCtrl;
-    ctrl.taskCtrl = controls.renderTaskListCtrl;
-    ctrl.onBtnClick = async function(name: string) {
-        if (name == "upload") {
-           const uploaded = await resource.actions.uploadMaterial()
-            ctrl.switchTab(uploaded.fileType, false);
-            ctrl.getCurrControl().loadPage(1);
-            return;
-        }
-        
-        showModal(name);
-    }
-    ctrl.onItemClick = function(name, record) {
-        if (name == "delete") return actions.deleteMaterial(record);
-        return actions.downloadMaterial(record)
-    }
-    
-    return () =>  (
-        <MaterialUI Controller={ ctrl } />
-    )
+    const ctrl = createController(resource, false, "");
+  
+    return () => (
+      <Material
+        Controller={ctrl}
+        slots={{
+          // MaterialItem: ()=>{
+          //   return <div>item</div>
+          // }
+        }}
+      ></Material>
+    );
   },
 });

+ 39 - 0
src/pages/website/Material2/modal.tsx

@@ -0,0 +1,39 @@
+import { defineComponent } from "vue";
+import Material from "./components/Material";
+import createController from "./controller";
+import { useResource } from "@/modules/resource";
+import { queenApi, useModal } from "queenjs";
+import { string } from "vue-types";
+
+
+const SelectMaterialDialog = defineComponent({
+  props: {
+    type: string<"image"|"video">()
+  },
+  setup(props) {
+    const resource = useResource();
+    const ctrl = createController(resource, true, props.type as string);
+    const model  = useModal();
+    
+    return () => (
+      <Material
+        Controller={ctrl}
+        slots={{
+          MaterialItem: ({record})=>{
+            return <div onClick={()=>{
+                model.submit(record);
+            }}>item</div>
+          }
+        }}
+      ></Material>
+    );
+  },
+});
+
+export async function SelectOneImage() {
+   return await queenApi.dialog(<SelectMaterialDialog type="image"/>, {title:"选择单张图片", width: "900px"})
+}
+
+export async function SelectOneVideo() {
+  return  await queenApi.dialog(<SelectMaterialDialog type="image" />,  {title:"选择单个视频", width: "900px"})
+}

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

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

+ 11 - 0
src/pages/website/Promotion2/components/PromotionController.ts

@@ -0,0 +1,11 @@
+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)
+    }
+}

+ 92 - 0
src/pages/website/Promotion2/components/PromotionItem.tsx

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

+ 44 - 0
src/pages/website/Promotion2/components/index.tsx

@@ -0,0 +1,44 @@
+import List from "@/components/AssetsList";
+import { useResource } from "@/modules/resource";
+import { onMounted } from "vue";
+import Header from "./Header";
+import PromotionItem from "./PromotionItem"
+import { defineUI } from "queenjs";
+import { any } from "vue-types";
+import { PromotionController } from "./PromotionController";
+
+
+export default defineUI({
+  props: {
+    Controller: any<PromotionController>().isRequired
+  },
+
+  slots:{
+    Header,
+    List
+  },
+  setup(props, {slots}) {
+    onMounted(() => {
+        props.Controller.ListCtrl.loadPage(1);
+    });
+    
+    return () => {
+      return (
+        <div>
+          <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);
+                }}/>
+            )}
+          />
+        </div>
+      );
+    };
+  },
+});

+ 27 - 0
src/pages/website/Promotion2/index.tsx

@@ -0,0 +1,27 @@
+import { useResource } from "@/modules/resource";
+import { defineComponent } from "vue";
+import PromotionUI from "./components";
+import { PromotionController } from "./components/PromotionController";
+import { PageListController } from "@queenjs/controllers";
+
+export default defineComponent({
+  setup() {
+    
+    const resource = useResource();
+    const ctrl= new PromotionController();
+    ctrl.ListCtrl = new PageListController(resource.config?.httpConfig);
+    ctrl.ListCtrl.setCrudPrefix("/h5")
+    ctrl.createPromotion = resource.actions.createPromotion;
+    
+    return () => (
+      <PromotionUI
+        Controller={ctrl}
+        slots={{
+          // MaterialItem: ()=>{
+          //   return <div>item</div>
+          // }
+        }}
+      ></PromotionUI>
+    );
+  },
+});

+ 10 - 6
src/pages/website/components/layout/LeftContent.tsx

@@ -1,5 +1,4 @@
 import { css, cx } from "@linaria/core";
-import { useAuth } from "@queenjs-modules/auth";
 import {
   IconCamera,
   IconDashboard,
@@ -10,16 +9,21 @@ import { Avatar, Divider } from "ant-design-vue";
 import { defineComponent } from "vue";
 import { array, object } from "vue-types";
 import Logo from "./Logo";
+import { UserController } from "./UserController";
+import { defineUI } from "queenjs";
 
-export default defineComponent({
-  setup() {
-    const auth = useAuth();
+export default defineUI({
+  props: {
+    Controller: object<UserController>().isRequired,
+  },
 
+  setup(props) {
+  
     const footerOptions: TextListProps[] = [
       {
         label: "退出",
         icon: IconDownload,
-        onClick: auth.actions.logout,
+        onClick: ()=>props.Controller.loginOut(),
       },
     ];
     const menuOptions = [
@@ -42,7 +46,7 @@ export default defineComponent({
       },
     ];
     return () => {
-      const { userInfo } = auth.store;
+      const { userInfo } = props.Controller.state;
 
       return (
         <div class={cx(rootStyles, "p-30px h-1/1 flex flex-col")}>

+ 8 - 0
src/pages/website/components/layout/UserController.ts

@@ -0,0 +1,8 @@
+import { reactive } from "vue";
+
+export class UserController {
+    state = reactive({userInfo:{} as any})
+    loginOut() {
+        console.log("loginout")
+    }
+}

+ 13 - 5
src/pages/website/components/layout/ProfileLayout.tsx → src/pages/website/components/layout/index.tsx

@@ -1,10 +1,18 @@
 import { css, cx } from "@linaria/core";
 import { Layout } from "ant-design-vue";
-import { defineComponent } from "vue";
-import LeftContent from "./LeftContent";
+import Left from "./LeftContent";
+import { defineUI } from "queenjs";
+import { UserController } from "./UserController";
+import { object } from "vue-types";
 
-export default defineComponent({
-  setup() {
+export default defineUI({
+  props: {
+    Controller: object<UserController>().isRequired,
+  },
+  slots: {
+    Left
+  },
+  setup(props, {slots}) {
     return () => {
       return (
         <Layout class={cx("!h-100vh flex", rootStyles)}>
@@ -13,7 +21,7 @@ export default defineComponent({
             theme="light"
             width="380px"
           >
-            <LeftContent />
+            <slots.Left Controller={props.Controller} />
           </Layout.Sider>
           <Layout.Content class="page_container scrollbar flex-1 my-20px rounded-20px p-50px overflow-y-auto">
             <router-view></router-view>

+ 22 - 0
src/pages/website/layout.tsx

@@ -0,0 +1,22 @@
+import Layout from "./components/layout";
+import { UserController } from "./components/layout/UserController";
+import { defineComponent } from "vue";
+import { useAuth } from "@queenjs-modules/auth";
+
+export default defineComponent({
+  setup() {
+    const auth = useAuth();
+    const ctrl= new UserController();
+    ctrl.loginOut = auth.actions.logout;
+
+    return () => {
+        ctrl.state.userInfo = auth.store.userInfo;
+        return (
+        <Layout
+            Controller={ctrl}
+            slots={{}}
+        ></Layout>
+    );
+    }
+  },
+});

+ 2 - 2
src/pages/website/router.ts

@@ -1,5 +1,5 @@
 import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
-import ProfileLayout from "./components/layout/ProfileLayout";
+import ProfileLayout from "./layout";
 
 const routes: Array<RouteRecordRaw> = [
   {
@@ -30,7 +30,7 @@ const routes: Array<RouteRecordRaw> = [
       {
         path: "/workbench/promotion",
         name: "promotion",
-        component: () => import("./Promotion"),
+        component: () => import("./Promotion2"),
       },
       {
         path: "/workstage/material",