liaojiaxing пре 5 месеци
комит
2c1ed1e6e9

+ 2 - 0
.env

@@ -0,0 +1,2 @@
+CHECK_TIMEOUT=5
+BASE_URL=http://a.dev.jbpm.shalu.com/

+ 9 - 0
.gitignore

@@ -0,0 +1,9 @@
+/node_modules
+/.env.local
+/.umirc.local.ts
+/config/config.local.ts
+/src/.umi
+/src/.umi-production
+/src/.umi-test
+/dist
+.swc

+ 2 - 0
.npmrc

@@ -0,0 +1,2 @@
+registry=https://registry.npmmirror.com/
+

+ 77 - 0
.umirc.ts

@@ -0,0 +1,77 @@
+import { defineConfig } from "umi";
+
+export default defineConfig({
+  base: "/",
+  publicPath: process.env.NODE_ENV === "production" ? "./" : "/",
+  outputPath: "marketplace",
+  esbuildMinifyIIFE: true,
+  favicons: [],
+  styles: ["//at.alicdn.com/t/c/font_4840729_ot8ca1ti90n.css"],
+  scripts: ["//at.alicdn.com/t/c/font_4840729_qpwqs1eruu.js"],
+  model: {},
+  metas: [
+    {
+      name: "viewport",
+      content:
+        "width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no",
+    },
+  ],
+  history: {
+    type: "hash",
+  },
+  plugins: [
+    require.resolve('@umijs/plugins/dist/unocss'),
+    require.resolve('@umijs/plugins/dist/model'),
+    require.resolve('@umijs/plugins/dist/initial-state'),
+    require.resolve('@umijs/plugins/dist/request'),
+  ],
+  // model: {},
+  unocss: {
+    watch: ["src/**/*.tsx"],
+  },
+  request: {
+    dataField: 'result',
+  },
+  proxy: {
+    "/api": {
+      target: "http://a.dev.jbpm.shalu.com/",
+      changeOrigin: true,
+      pathRewrite: { "^/api": "" },
+    },
+  },
+  routes: [
+    {
+      path: "/",
+      redirect: "/application",
+    },
+    {
+      path: "/application",
+      component: "application",
+    },
+    {
+      path: "/template",
+      component: "template",
+    },
+    {
+      // type: application or template
+      path: "/detail/:type/:id",
+      component: "detail",
+    },
+    {
+      path: '/management',
+      component: 'management',
+      layout: false
+    },
+    {
+      path: '/ai',
+      component: 'ai',
+      layout: false
+    },
+    {
+      path: '*',
+      component: '404',
+      layout: false
+    }
+  ],
+  npmClient: "pnpm",
+});

+ 34 - 0
package.json

@@ -0,0 +1,34 @@
+{
+  "private": true,
+  "author": "liaojiaxing <851982890@qq.com>",
+  "scripts": {
+    "dev": "umi dev",
+    "build": "umi build",
+    "postinstall": "umi setup",
+    "setup": "umi setup",
+    "start": "npm run dev"
+  },
+  "dependencies": {
+    "@ant-design/icons": "^5.6.1",
+    "@ant-design/pro-components": "^2.8.6",
+    "@ant-design/x": "^1.0.5",
+    "@emoji-mart/data": "^1.2.1",
+    "@emoji-mart/react": "^1.1.1",
+    "@unocss/reset": "66.1.0-beta.3",
+    "@wangeditor/editor": "^5.1.23",
+    "@wangeditor/editor-for-react": "^1.0.6",
+    "antd": "^5.24.2",
+    "dayjs": "^1.11.13",
+    "emoji-mart": "^5.6.0",
+    "umi": "^4.4.5"
+  },
+  "devDependencies": {
+    "@types/react": "^18.0.33",
+    "@types/react-dom": "^18.0.11",
+    "@umijs/plugins": "^4.4.5",
+    "@unocss/cli": "^0.62.4",
+    "cross-env": "^7.0.3",
+    "typescript": "^5.0.3",
+    "unocss": "^0.62.4"
+  }
+}

Разлика између датотеке није приказан због своје велике величине
+ 12052 - 0
pnpm-lock.yaml


+ 100 - 0
src/api/appStore.ts

@@ -0,0 +1,100 @@
+import { request } from "umi";
+import type { commonParams } from "@/api/index";
+
+/**
+ * 获取应用市场列表
+ * @param commonParams
+ * @returns
+ */
+export const GetAppList = (data: commonParams) => {
+  return request("/api/appStore/applicationTemplate/list", {
+    method: "POST",
+    data,
+  });
+};
+
+/**
+ * 获取应用详情
+ * @param commonParams
+ * @returns
+ */
+export const GetAppDetail = (data: { id: string}) => {
+  return request("/api/appStore/applicationTemplate/detail", {
+    method: "POST",
+    data,
+  });
+};
+
+export type AppItem = {
+  "name": string,
+  "applicationId": string,
+  "icon": string,
+  "desc": string,
+  "industries": string,
+  "applicationScenarios": string,
+  "tags": string,
+  "price": number,
+  "detail": string,
+  "isOnMarket": boolean,
+  "isFree": boolean
+}
+
+/**
+ * 新增修改应用
+ * @param AddItem
+ * @returns
+ */
+export const SaveOrUpdateApp = (data: AppItem) => {
+  return request("/api/appStore/applicationTemplate/saveOrUpdate", {
+    method: "POST",
+    data,
+  });
+};
+
+/**
+ * 上架应用
+ * @param ids
+ * @returns
+ */
+export const OnMarketApp = (data: {ids: string[]}) => {
+  return request("/api/appStore/applicationTemplate/onMarket", {
+    method: "POST",
+    data,
+  });
+};
+
+/**
+ * 下架应用
+ * @param ids
+ * @returns
+ */
+export const OffMarketApp = (data: {ids: string[]}) => {
+  return request("/api/appStore/applicationTemplate/offMarket", {
+    method: "POST",
+    data,
+  });
+};
+
+/**
+ * 批量删除应用
+ * @param ids
+ * @returns
+ */
+export const DeleteAppTemplate = (data: {ids: string[]}) => {
+  return request("/api/appStore/applicationTemplate/batchDelete", {
+    method: "POST",
+    data,
+  });
+};
+
+/**
+ * 批量物理删除应用
+ * @param ids
+ * @returns
+ */
+export const ForceDeleteAppTemplate = (data: {ids: string[]}) => {
+  return request("/api/appStore/applicationTemplate/forceDelete", {
+    method: "POST",
+    data,
+  });
+};

+ 69 - 0
src/api/index.ts

@@ -0,0 +1,69 @@
+import { request } from "umi";
+
+export type commonParams = {
+  currentPage: number;
+  pageSize: number;
+  orderByProperty?: string;
+  Ascending?: boolean;
+  totalPage?: number;
+  totalCount?: number;
+  filters?: any[];
+};
+
+/**
+ * 查询多语言
+ * @param data {maxCount: number, searchKey: string, searchLan: "zh-CN" | "en"}
+ * @returns
+ */
+export const ListLangBySearchKey = (data: {
+  maxCount: number;
+  searchKey: string;
+  searchLan: "zh-CN" | "en";
+}) => {
+  return request("/api/system/ListLangBySearchKey", {
+    method: "POST",
+    data,
+  });
+};
+
+/**
+ * 上传文件
+ * @param file 
+ * @returns
+ */
+export const UploadFile = (data: FormData) => {
+  return request<{
+    id: string
+  }[]>("/fileApi/File/UploadFiles", {
+    method: "POST",
+    headers: {
+      "Content-Type": "multipart/form-data",
+    },
+    data,
+  });
+};
+
+/**
+ * 获取文件
+ * @param fileId 文件id 
+ * @returns
+ */
+export const GetFile = (data: {fileId: string}) => {
+  return request("/File/Download", {
+    method: "GET",
+    params: data,
+    responseType: "blob",
+  });
+};
+
+/**
+ * 获取我的应用
+ * @param
+ * @returns
+ */
+export const GetMyAppList = (data: commonParams) => {
+  return request("/api/enterprise/myList", {
+    method: "POST",
+    data,
+  });
+};

+ 102 - 0
src/api/templateStore.ts

@@ -0,0 +1,102 @@
+import { request } from "umi";
+import type { commonParams } from "@/api/index";
+
+/**
+ * 获取模版市场列表
+ * @param commonParams
+ * @returns
+ */
+export const GetTemplateList = (data: commonParams) => {
+  return request("/api/templateStore/moduleTemplate/list", {
+    method: "POST",
+    data,
+  });
+};
+
+/**
+ * 获取模版详情
+ * @param id
+ * @returns
+ */
+export const GetTemplateDetail = (data: { id: string}) => {
+  return request("/api/templateStore/moduleTemplate/detail", {
+    method: "POST",
+    data,
+  });
+};
+
+type AddItem = {
+  "name": string,
+  "applicationId": string,
+  "moduleId": string,
+  "type": number, // 模板类型,系统设计、数据模型、页面设计、页面代码、流程模型、文档模型等
+  "icon": string,
+  "desc": string,
+  "industries": string,
+  "applicationScenarios": string,
+  "tags": string,
+  "price": number,
+  "detail": string,
+  "isOnMarket": boolean,
+  "isFree": boolean
+}
+
+/**
+ * 新增修改模版
+ * @param AddItem
+ * @returns
+ */
+export const SaveOrUpdateTemplate = (data: AddItem) => {
+  return request("/api/templateStore/moduleTemplate/saveOrUpdate", {
+    method: "POST",
+    data,
+  });
+};
+
+/**
+ * 上架模版
+ * @param ids
+ * @returns
+ */
+export const OnMarketTemplate = (data: {ids: string[]}) => {
+  return request("/api/templateStore/moduleTemplate/onMarket", {
+    method: "POST",
+    data,
+  });
+};
+
+/**
+ * 下架模版
+ * @param ids
+ * @returns
+ */
+export const OffMarketTemplate = (data: {ids: string[]}) => {
+  return request("/api/templateStore/moduleTemplate/offMarket", {
+    method: "POST",
+    data,
+  });
+};
+
+/**
+ * 批量删除应用
+ * @param ids
+ * @returns
+ */
+export const DeleteTemplate = (data: {ids: string[]}) => {
+  return request("/api/templateStore/moduleTemplate/batchDelete", {
+    method: "POST",
+    data,
+  });
+};
+
+/**
+ * 批量物理删除应用
+ * @param ids
+ * @returns
+ */
+export const ForceDeleteTemplate = (data: {ids: string[]}) => {
+  return request("/api/templateStore/moduleTemplate/forceDelete", {
+    method: "POST",
+    data,
+  });
+};

+ 84 - 0
src/app.ts

@@ -0,0 +1,84 @@
+import '@unocss/reset/sanitize/sanitize.css';
+import { message, notification } from 'antd';
+import type { RequestConfig } from 'umi';
+
+// 与后端约定的响应数据格式
+interface ResponseStructure {
+  code: number;
+  isSuccess: boolean;
+  isAuthorized: boolean;
+  result: any;
+  message: string;
+  error: string;
+  errorCode: string;
+}
+
+export const request: RequestConfig = {
+  timeout: 10000,
+  // other axios options you want
+  errorConfig: {
+    errorHandler(error: any, opts: any){
+      if (opts?.skipErrorHandler) throw error;
+      // 我们的 errorThrower 抛出的错误。
+      if (error.name === 'BizError') {
+        const errorInfo: ResponseStructure | undefined = error.info;
+        if (errorInfo) {
+          const { error: errorMessage } = errorInfo;
+          message.error(errorMessage);
+        }
+      } else if (error.response) {
+        // Axios 的错误
+        // 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
+        message.error(`Response status:${error.response.status}`);
+      } else if (error.request) {
+        // 请求已经成功发起,但没有收到响应
+        // \`error.request\` 在浏览器中是 XMLHttpRequest 的实例,
+        // 而在node.js中是 http.ClientRequest 的实例
+        message.error('请求无响应,请稍后再试!');
+      } else {
+        // 发送请求时出了点问题
+        message.error('请求错误,请稍后再试!');
+      }
+    },
+    errorThrower(res){
+      const { code, error: errorMsg, errorCode, isSuccess } = res;
+      if (!isSuccess) {
+        const error: any = new Error(errorMsg);
+        error.name = 'BizError';
+        error.info = { errorCode, errorMessage: errorMsg };
+        throw error; // 抛出自制的错误
+      }
+    }
+  },
+  requestInterceptors: [
+    (url, options) => {
+      const baseUrl = process.env.NODE_ENV === 'production' ? '' : '/api'//'http://ab.dev.jbpm.shalu.com' // https://edesign.shalu.com'
+      // const enterpriseCode = sessionStorage.getItem('enterpriseCode');
+      const enterpriseCode = 'a';
+      const token = localStorage.getItem('token_' + enterpriseCode);
+ 
+      if(token) {
+        if(!options.headers) {
+          options.headers = {}
+        }
+        options.headers.Authorization = token
+      }
+
+      return {
+        url: baseUrl + url,
+        options
+      }
+    }
+  ],
+  responseInterceptors: [
+    (response) => {
+      const {data = {} as any, config} = response;
+      if(data?.error) {
+        message.error(data.error);
+        return Promise.reject(data.error);
+      }
+      
+      return response;
+    }
+  ]
+};

BIN
src/assets/1.webp


BIN
src/assets/2.webp


BIN
src/assets/shalu-new1.png


+ 84 - 0
src/components/Editor.tsx

@@ -0,0 +1,84 @@
+import '@wangeditor/editor/dist/css/style.css' // 引入 css
+
+import React, { useState, useEffect } from 'react'
+import { Editor, Toolbar } from '@wangeditor/editor-for-react'
+import { IDomEditor, IEditorConfig, IToolbarConfig } from '@wangeditor/editor'
+import { UploadFile, GetFile } from "@/api"
+
+type InsertFnType = (url: string, poster: string) => void
+export default ({
+  html,
+  onChange
+}: {
+  html: string,
+  onChange: (html: string) => void
+}) => {
+  // editor 实例
+  const [editor, setEditor] = useState<IDomEditor | null>(null)
+
+  // 工具栏配置
+  const toolbarConfig: Partial<IToolbarConfig> = {}
+
+  // 自定义插入
+  const customInsert = (res: any, insertFn: InsertFnType) => {
+    console.log('自定义插入:', res);
+    // insertFn(url, alt, href)
+  };
+
+  // 自定义上传
+  const customUpload = async (file: File, insertFn: InsertFnType) => {
+    console.log('自定义上传:', file);
+    const form = new FormData();
+    form.append('file', file);
+    const res = await UploadFile(form);
+
+    const fileId = res?.[0]?.id;
+    insertFn(`/api/File/Download?fileId=${fileId}`, file.name);
+    return;
+  };
+
+  // 编辑器配置
+  const editorConfig: Partial<IEditorConfig> = {
+    placeholder: '请输入内容...',
+    MENU_CONF: {
+      uploadImage: {
+        customUpload,
+        customInsert
+      },
+      uploadVideo: {
+        customUpload,
+        customInsert
+      }
+    }
+  }
+
+  // 及时销毁 editor
+  useEffect(() => {
+    return () => {
+      if (editor == null) return
+      editor.destroy()
+      setEditor(null)
+    }
+  }, [editor])
+
+  return (
+    <>
+      <div style={{ border: '1px solid #ccc', zIndex: 100 }}>
+        <Toolbar
+          editor={editor}
+          defaultConfig={toolbarConfig}
+          mode="default"
+          style={{ borderBottom: '1px solid #ccc' }}
+        />
+        <Editor
+          defaultConfig={editorConfig}
+          value={html}
+          onCreated={setEditor}
+          onChange={(editor) => onChange?.(editor.getHtml())}
+          mode="default"
+          style={{ height: '500px', overflowY: 'hidden' }}
+        />
+      </div>
+    </>
+  )
+}

+ 43 - 0
src/components/ItemCard.tsx

@@ -0,0 +1,43 @@
+import React from "react";
+import { Button } from "antd";
+export default function AppItem(props: {
+  data: any;
+  onClick: (id: string) => void;
+}) {
+  return (
+    <div className="relative overflow-hidden pb-2 group col-span-1 bg-white border-2 border-solid border-transparent rounded-lg shadow-sm flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg">
+      <div className="flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0">
+        <div className="relative shrink-0">
+          <span
+            className="flex items-center justify-center relative rounded-lg grow-0 shrink-0 overflow-hidden w-10 h-10 text-[24px]"
+            style={{ background: "rgb(224, 242, 254)" }}
+          >
+            <img />
+          </span>
+        </div>
+        <div className="grow w-0 py-[1px]">
+          <div className="flex items-center text-sm leading-5 font-semibold text-text-secondary">
+            <div className="truncate" title="CRM客户关系管理系统">
+              CRM客户关系管理系统
+            </div>
+          </div>
+          <div className="flex items-center text-[10px] leading-[18px] text-text-tertiary font-medium">
+            <div className="truncate">作者 易码工坊</div>
+          </div>
+        </div>
+      </div>
+      <div className="description-wrapper h-[90px] px-[14px] text-xs leading-normal text-text-tertiary ">
+        <div className="line-clamp-4 group-hover:line-clamp-2">
+          面向中小型制造业、软高科、商贸企业的开箱即用的客户管理系统
+        </div>
+      </div>
+      <div className="hidden items-center flex-wrap min-h-[42px] px-[14px] pt-2 pb-[10px] bg-white group-hover:flex absolute bottom-0 left-0 right-0">
+        <div className="flex items-center w-full space-x-2">
+          <Button type="primary" size="small" className="w-full" onClick={() => props.onClick(props.data.id)}>
+            查看详情
+          </Button>
+        </div>
+      </div>
+    </div>
+  );
+}

+ 27 - 0
src/constants/index.ts

@@ -0,0 +1,27 @@
+// 行业选项
+export const INDUSTRIE_OPTIONS = [
+  {id: 1, label: '制造业', value: '制造业'},
+  {id: 2, label: '零售行业', value: '零售行业'},
+  {id: 3, label: '互联网科技', value: '互联网科技'},
+  {id: 4, label: '企业服务', value: '企业服务'},
+  {id: 5, label: '建筑工程', value: '建筑工程'},
+  {id: 6, label: '地产物业', value: '地产物业'},
+  {id: 7, label: '医疗健康', value: '医疗健康'},
+  {id: 8, label: '交通运输', value: '交通运输'},
+  {id: 9, label: '能源矿产', value: '能源矿产'},
+  {id: 10, label: '教育培训', value: '教育培训'},
+  {id: 11, label: '政府公益', value: '政府公益'}
+];
+
+// 应用场景
+export const APPLICATION_SCENARIOS_OPTIONS = [
+  {id: 1, label: '进销存/仓库', value: '进销存/仓库'},
+  {id: 2, label: 'CRM/仓库', value: 'CRM/仓库'},
+  {id: 3, label: 'ERP/生产', value: 'ERP/生产'},
+  {id: 4, label: '人事/行政', value: '人事/行政'},
+  {id: 5, label: '项目/任务', value: '项目/任务'},
+  {id: 6, label: '财务/报销', value: '财务/报销'},
+  {id: 7, label: '设备/巡检', value: '设备/巡检'},
+  {id: 8, label: '工单/售后', value: '工单/售后'},
+  {id: 9, label: '采招/供应', value: '采招/供应'}
+]

+ 3 - 0
src/global.less

@@ -0,0 +1,3 @@
+body {
+  background: #f2f4f7;
+}

+ 1 - 0
src/layouts/index.less

@@ -0,0 +1 @@
+

+ 45 - 0
src/layouts/index.tsx

@@ -0,0 +1,45 @@
+import { Link, Outlet, useLocation } from "umi";
+import logo from "@/assets/shalu-new1.png";
+import { Avatar, ConfigProvider } from "antd";
+import zhCN from "antd/locale/zh_CN";
+import "dayjs/locale/zh-cn";
+export default function Layout() {
+  const location = useLocation();
+
+  return (
+    <ConfigProvider locale={zhCN}>
+      <div>
+        <div className="header h-56px flex items-center justify-between border-0 border-b border-solid border-gray-200 px-8">
+          <img src={logo} alt="logo" className="h-48px" />
+
+          <ul className="menu flex items-center gap-x-24px">
+            <Link to="/" className="decoration-none">
+              <li
+                className={`nav-button ${location.pathname.includes("application") ? "nav-button-active" : ""} `}
+              >
+                <i className="iconfont icon-yingyong text-12px mr-4px" />
+                应用市场
+              </li>
+            </Link>
+            <Link to="/template" className="decoration-none">
+              <li
+                className={`nav-button ${location.pathname.includes("/template") ? "nav-button-active" : ""} `}
+              >
+                <i className="iconfont icon-mokuai text-12px mr-4px" />
+                模版市场
+              </li>
+            </Link>
+          </ul>
+
+          <div className="right">
+            <Avatar size={32}/>
+          </div>
+        </div>
+
+        <div className="h-[calc(100vh-56px)]">
+          <Outlet />
+        </div>
+      </div>
+    </ConfigProvider>
+  );
+}

+ 13 - 0
src/pages/404/index.tsx

@@ -0,0 +1,13 @@
+import React from 'react';
+import { Button, Result } from 'antd';
+
+const App: React.FC = () => (
+  <Result
+    status="404"
+    title="404"
+    subTitle="抱歉,当前页面不存在!"
+    extra={<Button type="primary" onClick={() => window.location.href = '/'}>返回首页</Button>}
+  />
+);
+
+export default App;

+ 207 - 0
src/pages/ai/Assistant.tsx

@@ -0,0 +1,207 @@
+import {
+  Bubble,
+  Conversations,
+  Prompts,
+  Sender,
+  Suggestion,
+  ThoughtChain,
+  XProvider,
+  useXAgent,
+  useXChat,
+  Welcome,
+} from "@ant-design/x";
+
+import { Card, Divider, Flex, Radio, Typography, App } from "antd";
+import React from "react";
+
+import {
+  BulbOutlined,
+  SmileOutlined,
+  UserOutlined,
+  CoffeeOutlined,
+  FireOutlined,
+  EditOutlined,
+  DeleteOutlined,
+  MessageOutlined,
+} from "@ant-design/icons";
+import type { GetProp } from "antd";
+import type { PromptsProps, ConversationsProps } from "@ant-design/x";
+
+const promptsItems: PromptsProps["items"] = [
+  {
+    key: "1",
+    icon: <CoffeeOutlined style={{ color: "#964B00" }} />,
+    description: "怎么创建我的应用?",
+  },
+  {
+    key: "2",
+    icon: <SmileOutlined style={{ color: "#FAAD14" }} />,
+    description: "页面设计器如何使用?",
+  },
+  {
+    key: "3",
+    icon: <FireOutlined style={{ color: "#FF4D4F" }} />,
+    description: "如何生成页面SQL?",
+  },
+];
+
+const roles: GetProp<typeof Bubble.List, "roles"> = {
+  assient: {
+    placement: "start",
+    avatar: {
+      icon: <i className="iconfont icon-AI1" />,
+      style: { background: "#fde3cf" },
+    },
+  },
+  user: {
+    placement: "end",
+    avatar: { icon: <UserOutlined />, style: { background: "#87d068" } },
+  },
+};
+
+export default () => {
+  const [value, setValue] = React.useState("");
+  const { message } = App.useApp();
+
+  const [agent] = useXAgent<{role: string, content: string}>({
+    baseURL: "http://localhost:3000/ai/chat",
+  });
+
+  const { onRequest, messages } = useXChat({ agent });
+
+  const menuConfig: ConversationsProps["menu"] = (conversation) => ({
+    items: [
+      {
+        label: "修改对话名称",
+        key: "operation1",
+        icon: <EditOutlined />,
+      },
+      {
+        label: "删除对话",
+        key: "operation3",
+        icon: <DeleteOutlined />,
+        danger: true,
+      },
+    ],
+    onClick: (menuInfo) => {
+      message.info(`Click ${conversation.key} - ${menuInfo.key}`);
+    },
+  });
+
+  const submitMessage = (message: string) => {
+    onRequest({role: "user", content: message});
+  };
+
+  const handlePromptItem = (item: any) => {
+    onRequest({role: "user", content: item.data.description});
+  };
+  console.log(messages)
+  return (
+    <>
+      <Card
+        className="w-full h-full"
+        styles={{
+          body: {
+            height: "calc(100% - 48px)",
+          },
+        }}
+        title={
+          <span>
+            <em-emoji id="*️⃣"></em-emoji>综合助手
+          </span>
+        }
+      >
+        <XProvider direction="ltr">
+          <Flex style={{ height: "100%" }} gap={12}>
+            <Conversations
+              style={{ width: 200 }}
+              defaultActiveKey="1"
+              menu={menuConfig}
+              items={[
+                {
+                  key: "1",
+                  label: "新的对话",
+                  icon: <MessageOutlined />,
+                },
+              ]}
+            />
+            <Divider type="vertical" style={{ height: "100%" }} />
+            <Flex vertical style={{ flex: 1 }} gap={8}>
+              <div className="flex-1">
+                {!messages.length ? (
+                  <>
+                    <Welcome
+                      icon="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*s5sNRo5LjfQAAAAAAAAAAAAADgCCAQ/fmt.webp"
+                      title="你好!我是易码工坊AI助手"
+                      description="关于易码工坊的所有问题,你都可以向我咨询~"
+                      className="mt-20 mb-10"
+                    />
+                    <Prompts
+                      title="✨ 你可以这样问我:"
+                      items={promptsItems}
+                      wrap
+                      onItemClick={handlePromptItem}
+                    />
+                  </>
+                ) : (
+                  <Bubble.List
+                    autoScroll
+                    roles={roles}
+                    items={
+                      []
+                    //   messages.map(({ id, message, status }) => ({
+                    //   key: id,
+                    //   loading: status === "loading",
+                    //   role: status === "user" ? "local" : "ai",
+                    //   content: message,
+                    // }))
+                  }
+                  />
+                )}
+              </div>
+              <Prompts
+                items={[
+                  {
+                    key: "1",
+                    icon: <BulbOutlined style={{ color: "#FFD700" }} />,
+                    label: "Ignite Your Creativity",
+                  },
+                  {
+                    key: "2",
+                    icon: <SmileOutlined style={{ color: "#52C41A" }} />,
+                    label: "Tell me a Joke",
+                  },
+                ]}
+                onItemClick={handlePromptItem}
+              />
+
+              <Suggestion
+                items={[{ label: "写一个应用介绍", value: "report" }]}
+                onSelect={submitMessage}
+              >
+                {({ onTrigger, onKeyDown }) => {
+                  return (
+                    <Sender
+                      value={value}
+                      onChange={(nextVal) => {
+                        if (nextVal === "/") {
+                          onTrigger();
+                        } else if (!nextVal) {
+                          onTrigger(false);
+                        }
+                        setValue(nextVal);
+                      }}
+                      onKeyDown={onKeyDown}
+                      placeholder='输入/获取快捷提示'
+                      onSubmit={submitMessage}
+                    />
+                  );
+                }}
+              </Suggestion>
+            </Flex>
+          </Flex>
+        </XProvider>
+      </Card>
+    </>
+  );
+};

+ 8 - 0
src/pages/ai/index.less

@@ -0,0 +1,8 @@
+.active {
+  border: .5px solid #eaecf0;
+  box-shadow: 0 1px 2px rgba(16, 24, 40, .05);
+  border-radius: 8px;
+  background: #fff;
+  color: #344054;
+  font-weight: 500;
+}

+ 96 - 0
src/pages/ai/index.tsx

@@ -0,0 +1,96 @@
+import React, { useState } from "react";
+import Assistant from "./Assistant";
+import data from "@emoji-mart/data";
+import { init } from "emoji-mart";
+import styles from "./index.less";
+
+init({ data });
+
+export default () => {
+  const assistantList = [
+    {
+      key: "1",
+      name: "综合助手",
+      icon: "iconfont icon-tuijian mr-1",
+    },
+    {
+      key: "2",
+      name: "系统设计",
+      icon: "iconfont icon-tuijian mr-1",
+    },
+    {
+      key: "3",
+      name: "数据模型",
+      icon: "iconfont icon-tuijian mr-1",
+    },
+    {
+      key: "4",
+      name: "页面设计",
+      icon: "iconfont icon-tuijian mr-1",
+    },
+    {
+      key: "5",
+      name: "流程设计",
+      icon: "iconfont icon-tuijian mr-1",
+    },
+    {
+      key: "6",
+      name: "文档管理",
+      icon: "iconfont icon-tuijian mr-1",
+    },
+  ];
+
+  const [active, setActive] = useState("1");
+  return (
+    <div className="flex h-full bg-gray-100 border-t border-gray-200 overflow-hidden">
+      <div className="w-fit sm:w-[216px] shrink-0 pt-6 px-4 border-gray-200 cursor-pointer">
+        <div className="mt-10">
+          <p className="pl-2 mobile:px-0 text-xs text-gray-500 break-all font-medium uppercase">
+            助手类型
+          </p>
+          <div
+            className="mt-3 space-y-1 overflow-y-auto overflow-x-hidden"
+            style={{ height: "calc(-100px + 100vh)" }}
+          >
+            {assistantList.map((item, index) => (
+              <div
+                key={index}
+                className={
+                  `flex h-8 items-center justify-between mobile:justify-center px-2 mobile:px-1 rounded-lg text-sm font-normal ${active === item.key ? styles.active : " hover:bg-gray-200"}`
+                }
+                onClick={() => setActive(item.key)}
+              >
+                <div className="flex items-center space-x-2 w-0 grow">
+                  <span
+                    className="flex items-center justify-center relative rounded-lg grow-0 shrink-0 overflow-hidden w-6 h-6 text-base"
+                    style={{ background: "rgb(213, 245, 246)" }}
+                  >
+                    <em-emoji id="*️⃣"></em-emoji>
+                  </span>
+                  <div
+                    className="overflow-hidden text-ellipsis whitespace-nowrap"
+                    title={item.name}
+                  >
+                    {item.name}
+                  </div>
+                </div>
+                <div className="shrink-0 h-6">
+                  <div className="inline-block" data-state="closed">
+                    <div className="style_btn__bbesM h-6 w-6 rounded-md border-none py-1"></div>
+                  </div>
+                </div>
+              </div>
+            ))}
+          </div>
+        </div>
+      </div>
+      <div className="grow w-0">
+        <div className="h-full py-2 pl-0 pr-2 sm:p-2">
+          <div className="h-full flex bg-white rounded-2xl shadow-md overflow-hidden false">
+            <Assistant />
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};

+ 91 - 0
src/pages/application/index.tsx

@@ -0,0 +1,91 @@
+import { useEffect, useState } from "react";
+import ItemCard from "@/components/ItemCard";
+import { Input } from "antd";
+import { GetAppList } from "@/api/appStore";
+import { useRequest, history } from "umi";
+import { INDUSTRIE_OPTIONS, APPLICATION_SCENARIOS_OPTIONS } from "@/constants";
+
+type SceneItem = { 
+  label: string;
+  value: string;
+  icon?: JSX.Element;
+}
+const industrys = [
+  { label: "全部行业", value: "all" },
+  ...INDUSTRIE_OPTIONS
+];
+
+const scenes: SceneItem[] = [
+  {
+    label: "推荐",
+    icon: <i className="iconfont icon-tuijian mr-1" />,
+    value: "recommend",
+  },
+  ...APPLICATION_SCENARIOS_OPTIONS
+];
+export default function Home() {
+  const [industryFilter, setIndustryFilter] = useState("all");
+  const [sceneFilter, setSceneFilter] = useState("recommend");
+  const { data, run, loading } = useRequest(GetAppList);
+
+  useEffect(() => {
+
+  }, [data]);
+
+  const handleToAppDetail = (id: string) => {
+    history.push(`/detail/application/${id}`);
+  }
+
+  return (
+    <div className="flex h-full">
+      <div className="left w-fit sm:w-[216px] shrink-0 pt-6 px-4 border-gray-200 border-0 border-r border-solid border-gray-200">
+        <ul className="flex flex-col gap-y-2">
+          {industrys.map((item) => (
+            <li
+              key={item.value}
+              className={`cursor-pointer text-14px text-secondary gap-2 flex items-center pc:justify-start pc:w-full mobile:justify-center mobile:w-fit h-9 px-3 mobile:px-2 rounded-lg ${industryFilter === item.value ? "bg-white font-semibold !text-primary shadow-xs" : ""}`}
+              onClick={() => setIndustryFilter(item.value)}
+            >
+              <span>{item.label}</span>
+            </li>
+          ))}
+        </ul>
+      </div>
+      <div className="right flex-1 pt-6 px-4 h-full flex flex-col">
+        <div className="shrink-0 pt-6 px-12">
+          <div className="mb-1 text-primary text-xl font-semibold">
+            探索应用模版
+          </div>
+          <div className="text-gray-500 text-sm">
+            使用这些模板应用程序,或根据模板自定义您自己的应用程序。
+          </div>
+        </div>
+        <div className="flex items-center justify-between mt-6 px-12">
+          <div className="flex space-x-1 text-[13px] flex-wrap">
+            {scenes.map((scene) => (
+              <div
+                key={scene.value}
+                className={`cursor-pointer px-3 py-[7px] h-[32px] rounded-lg font-medium leading-[18px] cursor-pointer ${scene.value === sceneFilter ? "bg-white shadow-xs text-primary-600 text-primary" : "border-transparent text-gray-700 hover:bg-gray-200"}`}
+                onClick={() => setSceneFilter(scene.value)}
+              >
+                {scene?.icon}
+                {scene.label}
+              </div>
+            ))}
+          </div>
+          <div>
+            <Input placeholder="搜索" prefix={<i className="iconfont icon-sousuo"/>}></Input>
+          </div>
+        </div>
+
+        <div className="relative flex flex-1 pb-6 flex-col overflow-auto bg-gray-100 shrink-0 grow mt-4">
+          <nav className="grid content-start shrink-0 gap-4 px-6 sm:px-12" style={{gridTemplateColumns: 'repeat(3,minmax(0,1fr))'}}>
+            {Array.from({ length: 21 }).map((item, index) => (
+              <ItemCard data={item} key={index} onClick={handleToAppDetail} />
+            ))}
+          </nav>
+        </div>
+      </div>
+    </div>
+  );
+}

+ 101 - 0
src/pages/detail/index.tsx

@@ -0,0 +1,101 @@
+import { Button, Form, Image, Input, Modal, Select } from "antd";
+import { LeftOutlined } from "@ant-design/icons";
+import { useState } from "react";
+import img2 from "@/assets/2.webp";
+import img1 from "@/assets/1.webp";
+import { useParams, useRequest } from "umi";
+import { GetAppDetail } from "@/api/appStore";
+import { GetTemplateDetail } from "@/api/templateStore";
+export default function detail() {
+  const [showAdvisory, setShowAdvisory] = useState(false);
+  const [form] = Form.useForm();
+
+  const handleSubmit = () => {
+    form.validateFields().then(() => {
+      setShowAdvisory(false);
+    });
+  };
+
+  const { id, type } = useParams();
+
+  const { data, loading } = useRequest(type === 'application' ? GetAppDetail : GetTemplateDetail);
+  const handleToApp = () => {
+  };
+
+  return (
+    <div className="detail  mx-auto pt-24px h-full overflow-y-auto">
+      <div className="absolute top-24px right-24px">
+        <Button type="text" icon={<i className="<LeftOutlined />"/>}/>
+      </div>
+      <div className="max-w-[1200px] mx-auto">
+        <div className="flex mb-32px">
+          <div className="w-128px h-128px rounded-20px bg-gray-200">
+            <Image className="" />
+          </div>
+          <div className="flex-1 mx-24px">
+            <div className="text-32px font-[600]">CRM客户管理系统</div>
+            <div className="text-secondary text-14px">
+              方案适用于多行业企业客户管理,在客户各环节布局提升,助销售快捷操作与多维度分析。优点有:提高效率,数据看板涵盖产品、销售、客户数据及销售趋势,助力企业掌握销售动态、优化策略、激励员工、提升综合效益。
+            </div>
+            <div className="inline-block bg-[#dcdde0] rounded-8px text-secondary px-8px py-4px mt-12px text-12px">
+              <i className="iconfont icon-qiyexinxi mr-8px" />
+              <span>易码工坊</span>
+            </div>
+          </div>
+          <div className="head-right flex flex-col justify-center items-center">
+            <Button type="primary" className="w-full mb-24px">
+              在线体验
+            </Button>
+            <Button className="w-full" onClick={() => setShowAdvisory(true)}>
+              购买咨询
+            </Button>
+          </div>
+        </div>
+        <div className="inline-block">
+          <div className="text-24px font-[600] mb-8px">方案详情</div>
+          <div className="w-60px h-4px bg-primary rounded-4px mx-auto" />
+        </div>
+
+        <div className="content m-y-32px overflow-hidden relative">
+          <img className="w-full mb-12px" src={img1} />
+          <img className="w-full" src={img2} />
+        </div>
+      </div>
+      <Modal title="购买咨询" width={500} open={showAdvisory} onCancel={() => setShowAdvisory(false)} onOk={handleSubmit}>
+        <Form labelCol={{ span: 5 }} autoComplete="off" form={form}>
+          <Form.Item
+            label="如何称呼您?"
+            name="name"
+            rules={[{ required: true, message: "请输入您的称呼!" }]}
+          >
+            <Input placeholder="请输入" maxLength={20} />
+          </Form.Item>
+          <Form.Item
+            label="联系方式"
+            name="phone"
+            rules={[{ required: true, message: "请输入您的联系方式!" }]}
+          >
+            <Input placeholder="请输入" maxLength={20} />
+          </Form.Item>
+          <Form.Item label="公司名称" name="company">
+            <Input placeholder="请输入" maxLength={100} />
+          </Form.Item>
+          <Form.Item label="企业规模" name="scale">
+            <Select
+              placeholder="请选择"
+              options={[
+                { label: "1-10人", value: "1-10人" },
+                { label: "11-50人", value: "11-50人" },
+                { label: "51-200人", value: "51-200人" },
+                { label: "200人以上", value: "200人以上" },
+              ]}
+            />
+          </Form.Item>
+          <Form.Item label="您的职务" name="position">
+            <Input placeholder="请输入" maxLength={20} />
+          </Form.Item>
+        </Form>
+      </Modal>
+    </div>
+  );
+}

+ 140 - 0
src/pages/management/AddAppDrawer.tsx

@@ -0,0 +1,140 @@
+import { PlusOutlined } from "@ant-design/icons";
+import {
+  DrawerForm,
+  ProForm,
+  ProFormSelect,
+  ProFormText,
+  ProFormUploadButton,
+  ProFormTextArea,
+  ProFormMoney,
+} from "@ant-design/pro-components";
+import type { FormInstance } from "@ant-design/pro-components";
+import { Button, Space, message } from "antd";
+import { useState, useRef } from "react";
+import Editor from "@/components/Editor";
+import { SaveOrUpdateApp } from "@/api/appStore";
+import { INDUSTRIE_OPTIONS, APPLICATION_SCENARIOS_OPTIONS } from "@/constants";
+import { customUploadRequest } from "@/utils";
+
+export default () => {
+  const [drawerVisit, setDrawerVisit] = useState(false);
+  const [html, setHtml] = useState("");
+  const formRef = useRef<FormInstance>();
+  const handleUploadChange = (info: any) => {
+    console.log(info);
+  };
+
+  return (
+    <>
+      <Space>
+        <Button
+          type="primary"
+          onClick={() => {
+            setDrawerVisit(true);
+          }}
+        >
+          <PlusOutlined />
+          新增应用
+        </Button>
+      </Space>
+
+      <DrawerForm
+        onOpenChange={setDrawerVisit}
+        title="新增应用"
+        open={drawerVisit}
+        onFinish={async (values: any) => {
+          await SaveOrUpdateApp({
+            ...values,
+            icon: values.icon[0]?.response?.id || '',
+            tags: values.tags?.replaceAll(',', ','),
+            detail: html
+          });
+          message.success("提交成功");
+          return true;
+        }}
+        drawerProps={{
+          maskClosable: false,
+        }}
+        formRef={formRef}
+        size="small"
+      >
+        <ProForm.Group title="基础信息">
+          <ProFormSelect
+            width="md"
+            name="applicationId"
+            label="应用"
+            placeholder="请输入名称"
+          />
+
+          <ProFormText
+            width="md"
+            name="name"
+            label="应用名称"
+            tooltip="最长为 30 位"
+            placeholder="请输入名称"
+            rules={[{ required: true , message: '请输入名称'}, { max: 30, message: '名称不能超过30个字符' }]}
+            required
+          />
+
+          <ProFormUploadButton
+            fieldProps={{
+              multiple: false,
+              name: "file",
+              listType: "picture-card",
+              accept: 'image/*',
+              customRequest: customUploadRequest
+            }}
+            max={1}
+            onChange={handleUploadChange}
+            name="icon"
+            label="图标"
+            required
+          />
+        </ProForm.Group>
+        <ProFormTextArea
+            name="desc"
+            label="应用简介"
+            placeholder="请输入简介,1000字以内"
+          />
+        <ProForm.Group>
+          <ProFormSelect
+            width="md"
+            name="industries"
+            label="所属行业"
+            placeholder="请选择"
+            mode="multiple"
+            options={INDUSTRIE_OPTIONS}
+          />
+          <ProFormSelect
+            width="md"
+            name="applicationScenarios"
+            label="应用场景"
+            placeholder="请选择"
+            mode="multiple"
+            options={APPLICATION_SCENARIOS_OPTIONS}
+          />
+          <ProFormText
+            width="md"
+            name="tags"
+            label="标签"
+            placeholder="多个标签用逗号分隔"
+          />
+          <ProFormMoney
+            width="md"
+            name="price"
+            label="定价"
+            fieldProps={{
+              defaultValue: 0,
+            }}
+            locale="zh-CN"
+            min={0}
+            placeholder="请输入"
+          />
+        </ProForm.Group>
+        <ProForm.Group title="详情信息">
+            <Editor html={html} onChange={setHtml} />
+        </ProForm.Group>
+      </DrawerForm>
+    </>
+  );
+};

+ 139 - 0
src/pages/management/AddModuleDrawer.tsx

@@ -0,0 +1,139 @@
+import { PlusOutlined } from "@ant-design/icons";
+import {
+  DrawerForm,
+  ProForm,
+  ProFormSelect,
+  ProFormText,
+  ProFormUploadButton,
+  ProFormTextArea,
+  ProFormMoney,
+} from "@ant-design/pro-components";
+import { Button, Space, message } from "antd";
+import { useState } from "react";
+import Editor from "@/components/Editor";
+import { customUploadRequest } from "@/utils";
+import { SaveOrUpdateTemplate } from "@/api/templateStore";
+import { APPLICATION_SCENARIOS_OPTIONS, INDUSTRIE_OPTIONS } from "@/constants";
+
+export default () => {
+  const [drawerVisit, setDrawerVisit] = useState(false);
+  const [html, setHtml] = useState("");
+
+  return (
+    <>
+      <Space>
+        <Button
+          type="primary"
+          onClick={() => {
+            setDrawerVisit(true);
+          }}
+        >
+          <PlusOutlined />
+          新增模版
+        </Button>
+      </Space>
+
+      <DrawerForm
+        onOpenChange={setDrawerVisit}
+        title="新增模版"
+        open={drawerVisit}
+        onFinish={async (values: any) => {
+          await SaveOrUpdateTemplate({
+            ...values,
+            icon: values.icon[0]?.response?.id || '',
+            tags: values.tags?.replaceAll(',', ','),
+            detail: html
+          });
+          message.success("提交成功");
+          return true;
+        }}
+        drawerProps={{
+          maskClosable: false,
+        }}
+        size="small"
+      >
+        <ProForm.Group title="基础信息">
+          <ProFormSelect
+            width="md"
+            name="applicationId"
+            label="所属应用"
+            placeholder="请选择"
+          />
+
+          <ProFormSelect
+            width="md"
+            name="type"
+            label="模版分类"
+            placeholder="请选择"
+          />
+
+          <ProFormSelect
+            width="md"
+            name="moduleId"
+            label="选择模版"
+            placeholder="请选择"
+          />
+
+          <ProFormText
+            width="md"
+            name="name"
+            label="模版名称"
+            tooltip="最长为 30 位"
+            placeholder="请输入名称"
+            rules={[{ required: true , message: '请输入名称'}, { max: 30, message: '名称不能超过30个字符' }]}
+          />
+
+          <ProFormUploadButton
+            max={1}
+            fieldProps={{
+              name: "file",
+              listType: "picture-card",
+              customRequest: customUploadRequest
+            }}
+            name="icon"
+            label="图标"
+          />
+        </ProForm.Group>
+        <ProFormTextArea
+          name="desc"
+          label="模版简介"
+          placeholder="请输入简介,1000字以内"
+        />
+        <ProForm.Group>
+          <ProFormSelect
+            width="md"
+            name="industries"
+            label="所属行业"
+            placeholder="请选择"
+            options={INDUSTRIE_OPTIONS}
+          />
+          <ProFormSelect
+            width="md"
+            name="applicationScenarios"
+            label="应用场景"
+            placeholder="请选择"
+            options={APPLICATION_SCENARIOS_OPTIONS}
+          />
+          <ProFormText
+            width="md"
+            name="tags"
+            label="标签"
+            placeholder="多个标签用逗号分隔"
+          />
+          <ProFormMoney
+            width="md"
+            name="name"
+            label="定价"
+            defaultValue={0}
+            min={0}
+            placeholder="请输入"
+            locale="zh-CN"
+          />
+        </ProForm.Group>
+        <ProForm.Group title="详情信息">
+          <Editor html={html} onChange={setHtml} />
+        </ProForm.Group>
+      </DrawerForm>
+    </>
+  );
+};

+ 152 - 0
src/pages/management/AppTab.tsx

@@ -0,0 +1,152 @@
+import { useState, useRef } from "react";
+import { ActionType, ProColumns, TableDropdown } from '@ant-design/pro-components';
+import { ProTable } from "@ant-design/pro-components";
+import { Button, Space, Tag } from 'antd';
+import AddAppDrawer from "./AddAppDrawer";
+import { GetAppList, DeleteAppTemplate } from "@/api/appStore";
+import type { AppItem } from "@/api/appStore";
+
+
+const columns: ProColumns<AppItem>[] = [
+  {
+    title: '应用名称',
+    dataIndex: 'name',
+    copyable: true,
+    ellipsis: true,
+  },
+  {
+    title: '作者',
+    dataIndex: 'title',
+    ellipsis: true,
+  },
+  {
+    title: '图标',
+    dataIndex: 'icon',
+    render: (_, record) => (
+      <img
+        src={record.icon}
+        alt=""
+        style={{
+          width: 30,
+          height: 30,
+          borderRadius: '50%',
+        }}
+      />
+    ),
+  },
+  {
+    disable: true,
+    title: '标签',
+    dataIndex: 'tags',
+    search: false,
+    renderFormItem: (_, { defaultRender }) => {
+      return defaultRender(_);
+    },
+    render: (_, record) => (
+      <Space>
+        {record.tags.split(',').map((tag) => (
+          <Tag color={'green'} key={tag}>
+            {tag}
+          </Tag>
+        ))}
+      </Space>
+    ),
+  },
+  {
+    title: '上架状态',
+    dataIndex: 'state',
+    filters: true,
+    onFilter: true,
+    ellipsis: true,
+    valueType: 'select',
+    valueEnum: {
+      all: { text: '全部' },
+      open: {
+        text: '已上架',
+        status: 'Error',
+      },
+      closed: {
+        text: '未上架',
+        status: 'Success',
+      },
+    },
+  },
+  {
+    title: '使用量',
+    dataIndex: 'useNum',
+  },
+  {
+    title: '操作',
+    valueType: 'option',
+    key: 'option',
+    render: (text, record, _, action) => [
+      <a>
+        编辑
+      </a>,
+      <a href={''} target="_blank" rel="noopener noreferrer" key="view">
+        详情
+      </a>,
+      <a href={'record'} target="_blank" rel="noopener noreferrer" key="upload">
+        上架
+      </a>,
+      <a href={'record'} target="_blank" rel="noopener noreferrer" key="delete">
+        删除
+      </a>,
+    ],
+  },
+];
+
+export default () => {
+  const actionRef = useRef<ActionType>();
+
+  return (
+    <ProTable<AppItem>
+      columns={columns}
+      actionRef={actionRef}
+      cardBordered
+      request={async (params, sort, filter) => {
+        console.log(params, sort, filter);
+        const res = await GetAppList({
+          currentPage: params.current || 1,
+          pageSize: params.pageSize || 10,
+          filters: [
+            {name: 'name', value: params?.name},
+            {name: 'applicationScenarios', value: params?.applicationScenarios},
+            {name: 'industries', value: params?.industries},
+            {name: 'isFree', value: params?.isFree},
+            {name: 'isOnMarket', value: params?.isOnMarket},
+            {name: 'idDel', value: params?.idDel},
+          ]
+        });
+        
+        return {
+          data: [],
+          success: true,
+          total: res.totalCount
+        }
+      }}
+      columnsState={{
+        persistenceKey: 'shalu-marketplace',
+        persistenceType: 'localStorage',
+        defaultValue: {
+          option: { fixed: 'right', disable: true },
+        },
+      }}
+      rowKey="id"
+      search={{
+        labelWidth: 'auto',
+      }}
+      options={{
+        setting: {
+          listsHeight: 400,
+        },
+      }}
+      pagination={{
+        pageSize: 10,
+        onChange: (page) => console.log(page),
+      }}
+      dateFormatter="string"
+      headerTitle={<AddAppDrawer />}
+    />
+  );
+}

+ 166 - 0
src/pages/management/ModuleTab.tsx

@@ -0,0 +1,166 @@
+import { useState, useRef } from "react";
+import { ActionType, ProColumns, TableDropdown } from '@ant-design/pro-components';
+import { ProTable } from "@ant-design/pro-components";
+import { Button, Space, Tag } from 'antd';
+import AddModuleDrawer from "./AddModuleDrawer";
+import { GetTemplateList, DeleteTemplate } from "@/api/templateStore";
+
+const columns: ProColumns<any>[] = [
+  {
+    title: '模版名称',
+    dataIndex: 'title',
+    copyable: true,
+    ellipsis: true,
+  },
+  {
+    title: '作者',
+    dataIndex: 'title',
+    copyable: true,
+    ellipsis: true,
+  },
+  {
+    title: '图标',
+    dataIndex: 'title',
+    render: (_, record) => (
+      <img
+        src={record.url}
+        alt=""
+        style={{
+          width: 30,
+          height: 30,
+          borderRadius: '50%',
+        }}
+      />
+    ),
+  },
+  {
+    title: '模版分类',
+    dataIndex: 'title',
+    copyable: true,
+    ellipsis: true,
+  },
+  {
+    disable: true,
+    title: '标签',
+    dataIndex: 'labels',
+    search: false,
+    renderFormItem: (_, { defaultRender }) => {
+      return defaultRender(_);
+    },
+    render: (_, record) => (
+      <Space>
+        {record.tags?.split(',').map((tag: string) => (
+          <Tag color='green' key={tag}>
+            {tag}
+          </Tag>
+        ))}
+      </Space>
+    ),
+  },
+  {
+    title: '上架状态',
+    dataIndex: 'state',
+    filters: true,
+    onFilter: true,
+    ellipsis: true,
+    valueType: 'select',
+    valueEnum: {
+      all: { text: '全部' },
+      open: {
+        text: '已上架',
+        status: 'Error',
+      },
+      closed: {
+        text: '未上架',
+        status: 'Success',
+      },
+    },
+  },
+  {
+    title: '使用量',
+    dataIndex: 'useNum',
+  },
+  {
+    title: '详情描述',
+    dataIndex: 'useNum',
+  },
+  {
+    title: '操作',
+    valueType: 'option',
+    key: 'option',
+    render: (text, record, _, action) => [
+      <a
+        onClick={() => {
+          action?.startEditable?.(record.id);
+        }}
+      >
+        编辑
+      </a>,
+      <a href={record.url} target="_blank" rel="noopener noreferrer" key="view">
+        详情
+      </a>,
+      <a href={record.url} target="_blank" rel="noopener noreferrer" key="upload">
+        上架
+      </a>,
+      <a href={record.url} target="_blank" rel="noopener noreferrer" key="delete">
+        删除
+      </a>,
+    ],
+  },
+];
+
+export default () => {
+  const actionRef = useRef<ActionType>();
+  return (
+    <ProTable
+      columns={columns}
+      actionRef={actionRef}
+      cardBordered
+      request={async (params, sort, filter) => {
+        console.log(sort, filter);
+        const res = await GetTemplateList({
+          currentPage: params.current || 1,
+          pageSize: params.pageSize || 10,
+          filters: [
+            {name: 'name', value: params?.name},
+            {name: 'applicationScenarios', value: params?.applicationScenarios},
+            {name: 'industries', value: params?.industries},
+            {name: 'isFree', value: params?.isFree},
+            {name: 'isOnMarket', value: params?.isOnMarket},
+          ]
+        })
+        
+        return {
+          success: true,
+          data: [],
+          total: 0
+        }
+      }}
+      columnsState={{
+        persistenceKey: 'shalu-marketplace',
+        persistenceType: 'localStorage',
+        defaultValue: {
+          option: { fixed: 'right', disable: true },
+        },
+        onChange(value) {
+          console.log('value: ', value);
+        },
+      }}
+      rowKey="id"
+      search={{
+        labelWidth: 'auto',
+      }}
+      options={{
+        setting: {
+          listsHeight: 400,
+        },
+      }}
+      pagination={{
+        pageSize: 5,
+        onChange: (page) => console.log(page),
+      }}
+      dateFormatter="string"
+      headerTitle={<AddModuleDrawer />}
+    />
+  );
+}

+ 15 - 0
src/pages/management/index.tsx

@@ -0,0 +1,15 @@
+import React from 'react'
+import { Tabs } from 'antd'
+import AppTab from './AppTab'
+import ModuleTab from './ModuleTab'
+
+export default () => {
+  return (
+    <div className='p-12px'>
+      <Tabs items={[
+        {key: 'app', label: '应用市场', children: <AppTab/>},
+        {key: 'module', label: '模版市场', children: <ModuleTab/>}
+      ]}/>
+    </div>
+  )
+}

+ 86 - 0
src/pages/template/index.tsx

@@ -0,0 +1,86 @@
+import { useState } from "react";
+import ItemCard from "@/components/ItemCard";
+import { APPLICATION_SCENARIOS_OPTIONS } from "@/constants";
+import { history } from "umi";
+
+type SceneItem = { 
+  label: string;
+  value: string;
+  icon?: JSX.Element;
+}
+
+const scenes: SceneItem[] = [
+  {
+    label: "推荐",
+    icon: <i className="iconfont icon-tuijian mr-1" />,
+    value: "recommend",
+  },
+  ...APPLICATION_SCENARIOS_OPTIONS
+];
+export default function Template() {
+  const categorys = [
+    { name: "全部行业", value: "all" },
+    { name: "系统设计", value: "manufacturing" },
+    { name: "数据模型", value: "retail" },
+    { name: "页面模型", value: "technology" },
+    { name: "文档模型", value: "enterprise" },
+    { name: "流程模型", value: "construction" },
+  ];
+
+  const [industryFilter, setIndustryFilter] = useState("all");
+  const [sceneFilter, setSceneFilter] = useState("recommend");
+
+  const handleToAppDetail = (id: string) => {
+    history.push(`/detail/application/${id}`);
+  }
+
+  return (
+    <div className="flex h-full">
+      <div className="left w-fit sm:w-[216px] shrink-0 pt-6 px-4 border-gray-200 border-0 border-r border-solid border-gray-200">
+        <ul className="flex flex-col gap-y-2">
+          {categorys.map((item) => (
+            <li
+              key={item.value}
+              className={`cursor-pointer text-14px text-secondary gap-2 flex items-center pc:justify-start pc:w-full mobile:justify-center mobile:w-fit h-9 px-3 mobile:px-2 rounded-lg ${industryFilter === item.value ? "bg-white font-semibold !text-primary shadow-xs" : ""}`}
+              onClick={() => setIndustryFilter(item.value)}
+            >
+              <span>{item.name}</span>
+            </li>
+          ))}
+        </ul>
+      </div>
+      <div className="right flex-1 pt-6 px-4 h-full flex flex-col">
+        <div className="shrink-0 pt-6 px-12">
+          <div className="mb-1 text-primary text-xl font-semibold">
+            探索模块模版
+          </div>
+          <div className="text-gray-500 text-sm">
+            使用这些模板,可以快速搭建你的系统功能模块。
+          </div>
+        </div>
+        <div className="flex items-center justify-between mt-6 px-12">
+          <div className="flex space-x-1 text-[13px] flex-wrap">
+            {scenes.map((scene) => (
+              <div
+                key={scene.value}
+                className={`cursor-pointer px-3 py-[7px] h-[32px] rounded-lg font-medium leading-[18px] cursor-pointer ${scene.value === sceneFilter ? "bg-white shadow-xs text-primary-600 text-primary" : "border-transparent text-gray-700 hover:bg-gray-200"}`}
+                onClick={() => setSceneFilter(scene.value)}
+              >
+                {scene.icon}
+                {scene.label}
+              </div>
+            ))}
+          </div>
+        </div>
+
+        <div className="relative flex flex-1 pb-6 flex-col overflow-auto bg-gray-100 shrink-0 grow mt-4">
+          <nav className="grid content-start shrink-0 gap-4 px-6 sm:px-12" style={{gridTemplateColumns: 'repeat(3,minmax(0,1fr))'}}>
+            {Array.from({ length: 21 }).map((item, index) => (
+              <ItemCard data={item} key={index} onClick={handleToAppDetail} />
+            ))}
+          </nav>
+        </div>
+      </div>
+    </div>
+  );
+}

+ 12 - 0
src/utils/index.ts

@@ -0,0 +1,12 @@
+import { UploadFile } from "@/api";
+
+export const customUploadRequest = async (options: any) => {
+  try {
+    const formData = new FormData();
+    formData.append("file", options.file);
+    const res =await UploadFile(formData);
+    options.onSuccess(res.result?.[0]);
+  } catch (error) {
+    options.onError(new Error("上传失败"));
+  }
+};

+ 3 - 0
tsconfig.json

@@ -0,0 +1,3 @@
+{
+  "extends": "./src/.umi/tsconfig.json"
+}

+ 21 - 0
typings.d.ts

@@ -0,0 +1,21 @@
+import 'umi/typings';
+import React from 'react';
+
+declare global {
+  namespace JSX {
+    interface IntrinsicElements {
+      'em-emoji': React.DetailedHTMLProps<
+        React.HTMLAttributes<HTMLElement>,
+        HTMLElement,
+        {
+          shortcodes: string,
+          native: string,
+          size: string,
+          fallback: string,
+          set: string,
+          skin: 1 | 2 | 3 | 4 | 5 | 6
+        }
+      >;
+    }
+  }
+}

+ 25 - 0
unocss.config.ts

@@ -0,0 +1,25 @@
+import {defineConfig, presetAttributify, presetUno} from 'unocss';
+
+export function createConfig({strict = true, dev = true} = {}) {
+  return defineConfig({
+    envMode: dev ? 'dev' : 'build', presets: [presetAttributify({strict}), presetUno()],
+    theme: {
+      colors: {
+        'primary': '#0e53e2',
+        'secondary': '#495464',
+        'text-secondary': '#354052',
+        'text-tertiary': '#676f83'
+      }
+    },
+    rules: [
+      ['flex-important', {display: 'flex !important'}],
+    ],
+    shortcuts: {
+      'flex-center': 'flex justify-center items-center',
+      'nav-button': 'cursor-pointer flex items-center text-secondary h-8 mr-0 sm:mr-3 px-3 h-8 rounded-xl text-sm shrink-0 font-medium false hover:bg-[#eaebef]',
+      'nav-button-active': 'shadow-md bg-[#fff] text-primary rounded-xl font-medium text-sm hover:bg-[#fff]',
+    }
+  });
+}
+
+export default createConfig();