瀏覽代碼

feat: 联调接口

liaojiaxing 2 月之前
父節點
當前提交
f37183a0c2

+ 1 - 1
.umirc.ts

@@ -30,7 +30,7 @@ export default defineConfig({
     watch: ["src/**/*.tsx"],
   },
   request: {
-    dataField: 'result',
+    dataField: '',
   },
   proxy: {
     "/api": {

+ 21 - 18
src/api/appStore.ts

@@ -18,7 +18,7 @@ export const GetAppList = (data: commonParams) => {
  * @param commonParams
  * @returns
  */
-export const GetAppDetail = (data: { id: string}) => {
+export const GetAppDetail = (data: { id: string }) => {
   return request("/api/appStore/applicationTemplate/detail", {
     method: "POST",
     data,
@@ -26,18 +26,21 @@ export const GetAppDetail = (data: { id: string}) => {
 };
 
 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
-}
+  id?: string;
+  name: string;
+  applicationId: string;
+  icon: string;
+  desc: string;
+  industries: string;
+  applicationScenarios: string;
+  tags: string;
+  price: number;
+  detail: string;
+  isOnMarket: boolean;
+  isFree: boolean;
+  version?: string;
+  trialUrl?: string;
+};
 
 /**
  * 新增修改应用
@@ -56,7 +59,7 @@ export const SaveOrUpdateApp = (data: AppItem) => {
  * @param ids
  * @returns
  */
-export const OnMarketApp = (data: {ids: string[]}) => {
+export const OnMarketApp = (data: { ids: string[] }) => {
   return request("/api/appStore/applicationTemplate/onMarket", {
     method: "POST",
     data,
@@ -68,7 +71,7 @@ export const OnMarketApp = (data: {ids: string[]}) => {
  * @param ids
  * @returns
  */
-export const OffMarketApp = (data: {ids: string[]}) => {
+export const OffMarketApp = (data: { ids: string[] }) => {
   return request("/api/appStore/applicationTemplate/offMarket", {
     method: "POST",
     data,
@@ -80,7 +83,7 @@ export const OffMarketApp = (data: {ids: string[]}) => {
  * @param ids
  * @returns
  */
-export const DeleteAppTemplate = (data: {ids: string[]}) => {
+export const DeleteAppTemplate = (data: { ids: string[] }) => {
   return request("/api/appStore/applicationTemplate/batchDelete", {
     method: "POST",
     data,
@@ -92,9 +95,9 @@ export const DeleteAppTemplate = (data: {ids: string[]}) => {
  * @param ids
  * @returns
  */
-export const ForceDeleteAppTemplate = (data: {ids: string[]}) => {
+export const ForceDeleteAppTemplate = (data: { ids: string[] }) => {
   return request("/api/appStore/applicationTemplate/forceDelete", {
     method: "POST",
     data,
   });
-};
+};

+ 1 - 3
src/api/index.ts

@@ -32,9 +32,7 @@ export const ListLangBySearchKey = (data: {
  * @returns
  */
 export const UploadFile = (data: FormData) => {
-  return request<{
-    id: string
-  }[]>("/fileApi/File/UploadFiles", {
+  return request("/fileApi/File/UploadFiles", {
     method: "POST",
     headers: {
       "Content-Type": "multipart/form-data",

+ 9 - 9
src/app.ts

@@ -17,6 +17,15 @@ export const request: RequestConfig = {
   timeout: 10000,
   // other axios options you want
   errorConfig: {
+    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; // 抛出自制的错误
+      }
+    },
     errorHandler(error: any, opts: any){
       if (opts?.skipErrorHandler) throw error;
       // 我们的 errorThrower 抛出的错误。
@@ -40,15 +49,6 @@ export const request: RequestConfig = {
         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) => {

文件差異過大導致無法顯示
+ 1 - 0
src/assets/no-data.svg


+ 2 - 2
src/components/Editor.tsx

@@ -31,8 +31,8 @@ export default ({
     const form = new FormData();
     form.append('file', file);
     const res = await UploadFile(form);
-
-    const fileId = res?.[0]?.id;
+    
+    const fileId = res?.result?.[0]?.id;
     insertFn(`/api/File/Download?fileId=${fileId}`, file.name);
     return;
   };

+ 46 - 6
src/components/ItemCard.tsx

@@ -1,6 +1,10 @@
 import React from "react";
 import { Button } from "antd";
-export default function AppItem(props: {
+import { TagOutlined } from "@ant-design/icons";
+export default function AppItem({
+  data,
+  onClick,
+}: {
   data: any;
   onClick: (id: string) => void;
 }) {
@@ -12,28 +16,64 @@ export default function AppItem(props: {
             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 />
+            <img className="w-full h-full" src={`/api/File/Download?fileId=${data?.icon}`}/>
           </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客户关系管理系统
+              {data?.name}
             </div>
           </div>
           <div className="flex items-center text-[10px] leading-[18px] text-text-tertiary font-medium">
-            <div className="truncate">作者 易码工坊</div>
+            <div className="truncate">作者 {data?.createByName}</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">
-          面向中小型制造业、软高科、商贸企业的开箱即用的客户管理系统
+          {data?.desc}
+        </div>
+      </div>
+      <div>
+        <div className="flex items-center h-5 px-[14px] text-12px">
+          <div className="flex items-center space-x-1 text-text-tertiary">
+            <svg
+              viewBox="0 0 24 24"
+              xmlns="http://www.w3.org/2000/svg"
+              width="24"
+              height="24"
+              fill="currentColor"
+              className="remixicon shrink-0 w-3 h-3"
+            >
+              <path d="M9 2V4H5L4.999 14H18.999L19 4H15V2H20C20.5523 2 21 2.44772 21 3V21C21 21.5523 20.5523 22 20 22H4C3.44772 22 3 21.5523 3 21V3C3 2.44772 3.44772 2 4 2H9ZM18.999 16H4.999L5 20H19L18.999 16ZM17 17V19H15V17H17ZM13 2V7H16L12 11L8 7H11V2H13Z"></path>
+            </svg>
+            <div className="system-xs-regular">339</div>
+          </div>
+          <div className="mx-2 text-text-quaternary system-xs-regular">·</div>
+          <div className="flex flex-wrap space-x-2 h-4 overflow-hidden">
+            {
+              (data?.tags?.split(",") || []).map((tag: string) => {
+                return <div
+                className="flex space-x-1 system-xs-regular max-w-[120px] overflow-hidden"
+                title={`# ${tag}`}
+              >
+                <span className="text-text-quaternary">#</span>
+                <span className="truncate text-text-tertiary">{tag}</span>
+              </div>
+              })
+            }
+          </div>
         </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
+            type="primary"
+            size="small"
+            className="w-full"
+            onClick={() => onClick(data.id)}
+          >
             查看详情
           </Button>
         </div>

+ 11 - 1
src/constants/index.ts

@@ -24,4 +24,14 @@ export const APPLICATION_SCENARIOS_OPTIONS = [
   {id: 7, label: '设备/巡检', value: '设备/巡检'},
   {id: 8, label: '工单/售后', value: '工单/售后'},
   {id: 9, label: '采招/供应', value: '采招/供应'}
-]
+];
+
+// 模块模版类型
+export const MODULE_TEMPLATE_TYPE = [
+  {id: 1, label: '系统设计', value: '系统设计'},
+  {id: 2, label: '数据模型', value: '数据模型'},
+  {id: 3, label: '页面设计', value: '页面设计'},
+  {id: 4, label: '页面代码', value: '页面代码'},
+  {id: 5, label: '文档模版', value: '文档模版'},
+  {id: 6, label: '流程模版', value: '流程模版'},
+];

+ 6 - 4
src/layouts/index.tsx

@@ -3,6 +3,8 @@ import logo from "@/assets/shalu-new1.png";
 import { Avatar, ConfigProvider } from "antd";
 import zhCN from "antd/locale/zh_CN";
 import "dayjs/locale/zh-cn";
+import { UserOutlined } from "@ant-design/icons";
+
 export default function Layout() {
   const location = useLocation();
 
@@ -10,9 +12,9 @@ export default function Layout() {
     <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" />
+          <img src={logo} alt="logo" className="h-48px w-200px" />
 
-          <ul className="menu flex items-center gap-x-24px">
+          <ul className="menu flex items-center pl-0 gap-x-24px">
             <Link to="/" className="decoration-none">
               <li
                 className={`nav-button ${location.pathname.includes("application") ? "nav-button-active" : ""} `}
@@ -31,8 +33,8 @@ export default function Layout() {
             </Link>
           </ul>
 
-          <div className="right">
-            <Avatar size={32}/>
+          <div className="right w-200px text-right">
+            <Avatar size={32} icon={<UserOutlined />} />
           </div>
         </div>
 

+ 59 - 18
src/pages/application/index.tsx

@@ -1,19 +1,17 @@
 import { useEffect, useState } from "react";
 import ItemCard from "@/components/ItemCard";
-import { Input } from "antd";
+import { Input, Empty, Spin } from "antd";
 import { GetAppList } from "@/api/appStore";
 import { useRequest, history } from "umi";
 import { INDUSTRIE_OPTIONS, APPLICATION_SCENARIOS_OPTIONS } from "@/constants";
+import noDataImg from "@/assets/no-data.svg";
 
-type SceneItem = { 
+type SceneItem = {
   label: string;
   value: string;
   icon?: JSX.Element;
-}
-const industrys = [
-  { label: "全部行业", value: "all" },
-  ...INDUSTRIE_OPTIONS
-];
+};
+const industrys = [{ label: "全部行业", value: "all" }, ...INDUSTRIE_OPTIONS];
 
 const scenes: SceneItem[] = [
   {
@@ -21,20 +19,48 @@ const scenes: SceneItem[] = [
     icon: <i className="iconfont icon-tuijian mr-1" />,
     value: "recommend",
   },
-  ...APPLICATION_SCENARIOS_OPTIONS
+  ...APPLICATION_SCENARIOS_OPTIONS,
 ];
 export default function Home() {
+  const [search, setSearch] = useState("");
   const [industryFilter, setIndustryFilter] = useState("all");
   const [sceneFilter, setSceneFilter] = useState("recommend");
-  const { data, run, loading } = useRequest(GetAppList);
+  const { data, run, loading } = useRequest(GetAppList, {
+    defaultParams: [
+      {
+        currentPage: 1,
+        pageSize: 1000,
+        filters: [
+          { name: "isDel", value: 0 },
+          { name: "isOnMarket", value: 1 },
+        ],
+      },
+    ],
+  });
 
   useEffect(() => {
-
-  }, [data]);
+    run({
+      currentPage: 1,
+      pageSize: 1000,
+      filters: [
+        { name: "isDel", value: 0 },
+        { name: "isOnMarket", value: 1 },
+        { name: "name", value: search },
+        {
+          name: "industries",
+          value: industryFilter === "all" ? "" : industryFilter,
+        },
+        {
+          name: "applicationScenarios",
+          value: sceneFilter === "recommend" ? "" : sceneFilter,
+        },
+      ],
+    });
+  }, [industryFilter, sceneFilter, search]);
 
   const handleToAppDetail = (id: string) => {
     history.push(`/detail/application/${id}`);
-  }
+  };
 
   return (
     <div className="flex h-full">
@@ -74,16 +100,31 @@ export default function Home() {
             ))}
           </div>
           <div>
-            <Input placeholder="搜索" prefix={<i className="iconfont icon-sousuo"/>}></Input>
+            <Input
+              value={search}
+              onChange={(e) => setSearch(e.target.value)}
+              placeholder="搜索"
+              prefix={<i className="iconfont icon-sousuo" />}
+              allowClear
+            ></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>
+          <Spin spinning={loading}>
+            <nav
+              className="grid content-start shrink-0 gap-4 px-6 sm:px-12"
+              style={{ gridTemplateColumns: "repeat(3,minmax(0,1fr))" }}
+            >
+              {(data?.result?.model || []).map((item: any, index: number) => (
+                <ItemCard data={item} key={index} onClick={handleToAppDetail} />
+              ))}
+            </nav>
+
+            {!data?.result.model.length && !loading && (
+              <Empty description="暂无数据" image={noDataImg}/>
+            )}
+          </Spin>
         </div>
       </div>
     </div>

+ 57 - 36
src/pages/detail/index.tsx

@@ -1,65 +1,86 @@
-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 { Button, Form, Image, Input, Modal, Select, Skeleton, Breadcrumb } from "antd";
+import type { BreadcrumbProps } from "antd";
+import { useMemo, useState } from "react";
+import { useParams, useRequest, history } 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(() => {
+    form.validateFields().then((values) => {
+
       setShowAdvisory(false);
     });
   };
 
   const { id, type } = useParams();
 
-  const { data, loading } = useRequest(type === 'application' ? GetAppDetail : GetTemplateDetail);
+  const { data, loading } = useRequest<{result: any}>(type === 'application' ? GetAppDetail : GetTemplateDetail, {
+    defaultParams: [{
+      id: id
+    }]
+  });
+
   const handleToApp = () => {
+    if(data?.result?.trialUrl) {
+      window.open(data?.result?.trialUrl, "_blank");
+    }
   };
 
+  const handleBack = () => {
+    history.back();
+  }
+
+  const breadcrumbItems: BreadcrumbProps['items'] = useMemo(() => {
+    return [
+      { key: 'all', title: <a onClick={handleBack}>全部</a>},
+      { key: 'current', title: data?.result.name}
+    ]
+  }, [data]);
+
   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="detail relative mx-auto pt-24px h-full overflow-y-auto">
       <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">
-              方案适用于多行业企业客户管理,在客户各环节布局提升,助销售快捷操作与多维度分析。优点有:提高效率,数据看板涵盖产品、销售、客户数据及销售趋势,助力企业掌握销售动态、优化策略、激励员工、提升综合效益。
+        <Breadcrumb className="mb-24px" items={breadcrumbItems}/>
+        <Skeleton avatar active loading={loading}>
+          <div className="flex mb-32px">
+            <div className="w-128px h-128px rounded-20px bg-gray-200 overflow-hidden flex justify-center items-center">
+              <Image className="w-full h-full" preview={false} src={data?.result?.icon ? `/api/File/Download?fileId=${data?.result?.icon}` : ''}/>
             </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 className="flex-1 mx-24px">
+              <div className="text-32px font-[600]">{data?.result?.name}</div>
+              <div className="text-secondary text-14px">
+                {data?.result?.desc || '暂无描述'}
+              </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>{data?.result?.createByName || '易码工坊'}</span>
+              </div>
+            </div>
+            <div className="head-right flex flex-col justify-center items-center">
+              <Button type="primary" className="w-full mb-24px" onClick={handleToApp}>
+                在线体验
+              </Button>
+              <Button className="w-full" onClick={() => setShowAdvisory(true)}>
+                购买咨询
+              </Button>
             </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>
+        </Skeleton>
         <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>
+        <Skeleton active loading={loading}>
+          <div className="content m-y-32px overflow-hidden relative">
+            <div dangerouslySetInnerHTML={{__html: data?.result?.detail || ''}}/>
+          </div>
+        </Skeleton>
+        
       </div>
       <Modal title="购买咨询" width={500} open={showAdvisory} onCancel={() => setShowAdvisory(false)} onOk={handleSubmit}>
         <Form labelCol={{ span: 5 }} autoComplete="off" form={form}>

+ 63 - 20
src/pages/management/AddAppDrawer.tsx

@@ -10,19 +10,47 @@ import {
 } 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 { useState, useRef, useEffect, useMemo } from "react";
 import Editor from "@/components/Editor";
-import { SaveOrUpdateApp } from "@/api/appStore";
+import { AppItem, SaveOrUpdateApp } from "@/api/appStore";
 import { INDUSTRIE_OPTIONS, APPLICATION_SCENARIOS_OPTIONS } from "@/constants";
 import { customUploadRequest } from "@/utils";
 
-export default () => {
+export default ({
+  onSuccess,
+  editData,
+}: {
+  onSuccess: () => void;
+  editData?: AppItem;
+}) => {
   const [drawerVisit, setDrawerVisit] = useState(false);
   const [html, setHtml] = useState("");
   const formRef = useRef<FormInstance>();
-  const handleUploadChange = (info: any) => {
-    console.log(info);
-  };
+
+  useEffect(() => {
+    setDrawerVisit(!!editData);
+  }, [editData]);
+
+  const initialValues = useMemo(() => {
+    if (editData) {
+      return {
+        ...editData,
+        industries: JSON.parse(editData.industries),
+        applicationScenarios: JSON.parse(editData.applicationScenarios),
+        icon: editData.icon
+          ? [
+              {
+                uid: editData.icon,
+                name: editData.icon,
+                status: "done",
+                url: `/api/File/Download?fileId=${editData.icon}`,
+              },
+            ]
+          : [],
+      };
+    }
+    return {};
+  }, [editData]);
 
   return (
     <>
@@ -40,18 +68,23 @@ export default () => {
 
       <DrawerForm
         onOpenChange={setDrawerVisit}
-        title="新增应用"
+        title={editData ? "编辑应用" : "新增应用"}
         open={drawerVisit}
         onFinish={async (values: any) => {
           await SaveOrUpdateApp({
             ...values,
-            icon: values.icon[0]?.response?.id || '',
-            tags: values.tags?.replaceAll(',', ','),
-            detail: html
+            id: editData?.id,
+            version: editData?.version,
+            icon: values.icon[0]?.response?.id || values.icon[0]?.uid || "",
+            tags: values.tags?.replaceAll(",", ","),
+            detail: html,
+            isFree: !values?.price,
           });
+          onSuccess();
           message.success("提交成功");
           return true;
         }}
+        initialValues={initialValues}
         drawerProps={{
           maskClosable: false,
         }}
@@ -72,30 +105,40 @@ export default () => {
             label="应用名称"
             tooltip="最长为 30 位"
             placeholder="请输入名称"
-            rules={[{ required: true , message: '请输入名称'}, { max: 30, message: '名称不能超过30个字符' }]}
+            rules={[
+              { required: true, message: "请输入名称" },
+              { max: 30, message: "名称不能超过30个字符" },
+            ]}
             required
           />
-
+          <ProFormText
+            width="md"
+            name="trialUrl"
+            label="试用地址"
+            placeholder="请输入名称"
+            rules={[
+              { max: 100, message: "名称不能超过100个字符" },
+            ]}
+          />
           <ProFormUploadButton
             fieldProps={{
               multiple: false,
               name: "file",
               listType: "picture-card",
-              accept: 'image/*',
-              customRequest: customUploadRequest
+              accept: "image/*",
+              customRequest: customUploadRequest,
             }}
             max={1}
-            onChange={handleUploadChange}
             name="icon"
             label="图标"
             required
           />
         </ProForm.Group>
         <ProFormTextArea
-            name="desc"
-            label="应用简介"
-            placeholder="请输入简介,1000字以内"
-          />
+          name="desc"
+          label="应用简介"
+          placeholder="请输入简介,1000字以内"
+        />
         <ProForm.Group>
           <ProFormSelect
             width="md"
@@ -132,7 +175,7 @@ export default () => {
           />
         </ProForm.Group>
         <ProForm.Group title="详情信息">
-            <Editor html={html} onChange={setHtml} />
+          <Editor html={html} onChange={setHtml} />
         </ProForm.Group>
       </DrawerForm>
     </>

+ 20 - 5
src/pages/management/AddModuleDrawer.tsx

@@ -13,9 +13,15 @@ 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";
+import { APPLICATION_SCENARIOS_OPTIONS, INDUSTRIE_OPTIONS, MODULE_TEMPLATE_TYPE } from "@/constants";
 
-export default () => {
+export default ({
+  onSuccess,
+  editData,
+}: {
+  onSuccess: () => void;
+  editData?: any;
+}) => {
   const [drawerVisit, setDrawerVisit] = useState(false);
   const [html, setHtml] = useState("");
 
@@ -40,10 +46,14 @@ export default () => {
         onFinish={async (values: any) => {
           await SaveOrUpdateTemplate({
             ...values,
+            id: editData?.id,
+            version: editData?.version,
             icon: values.icon[0]?.response?.id || '',
             tags: values.tags?.replaceAll(',', ','),
-            detail: html
+            detail: html,
+            isFree: !editData?.price,
           });
+          onSuccess();
           message.success("提交成功");
           return true;
         }}
@@ -65,6 +75,7 @@ export default () => {
             name="type"
             label="模版分类"
             placeholder="请选择"
+            options={MODULE_TEMPLATE_TYPE}
           />
 
           <ProFormSelect
@@ -86,9 +97,11 @@ export default () => {
           <ProFormUploadButton
             max={1}
             fieldProps={{
+              multiple: false,
               name: "file",
               listType: "picture-card",
-              customRequest: customUploadRequest
+              accept: "image/*",
+              customRequest: customUploadRequest,
             }}
             name="icon"
             label="图标"
@@ -105,6 +118,7 @@ export default () => {
             name="industries"
             label="所属行业"
             placeholder="请选择"
+            mode="multiple"
             options={INDUSTRIE_OPTIONS}
           />
           <ProFormSelect
@@ -112,6 +126,7 @@ export default () => {
             name="applicationScenarios"
             label="应用场景"
             placeholder="请选择"
+            mode="multiple"
             options={APPLICATION_SCENARIOS_OPTIONS}
           />
           <ProFormText
@@ -122,7 +137,7 @@ export default () => {
           />
           <ProFormMoney
             width="md"
-            name="name"
+            name="price"
             label="定价"
             defaultValue={0}
             min={0}

+ 232 - 108
src/pages/management/AppTab.tsx

@@ -1,103 +1,216 @@
 import { useState, useRef } from "react";
-import { ActionType, ProColumns, TableDropdown } from '@ant-design/pro-components';
+import {
+  ActionType,
+  ProColumns,
+  TableDropdown,
+} from "@ant-design/pro-components";
 import { ProTable } from "@ant-design/pro-components";
-import { Button, Space, Tag } from 'antd';
+import { Popconfirm, Tag, message } from "antd";
 import AddAppDrawer from "./AddAppDrawer";
-import { GetAppList, DeleteAppTemplate } from "@/api/appStore";
+import {
+  GetAppList,
+  DeleteAppTemplate,
+  OffMarketApp,
+  OnMarketApp,
+} from "@/api/appStore";
 import type { AppItem } from "@/api/appStore";
+import { history } from "umi";
 
+export default () => {
+  const actionRef = useRef<ActionType>();
+  const [editData, setEditData] = useState<AppItem>();
+  const handleToDetail = (id?: string) => {
+    history.push(`/detail/application/${id}`);
+  };
+
+  const handleOffMarket = async (id?: string) => {
+    if (id) {
+      await OffMarketApp({ ids: [id] });
+      actionRef.current?.reload();
+      message.success("下架成功");
+    }
+  };
+
+  const handleOnMarket = async (id?: string) => {
+    if (id) {
+      await OnMarketApp({ ids: [id] });
+      actionRef.current?.reload();
+      message.success("上架成功");
+    }
+  };
+
+  const handleDelete = async (id?: string) => {
+    if (id) {
+      await DeleteAppTemplate({ ids: [id] });
+      actionRef.current?.reload();
+      message.success("删除成功");
+    }
+  };
 
-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(_);
+  const columns: ProColumns<AppItem>[] = [
+    {
+      title: "图标",
+      dataIndex: "icon",
+      search: false,
+      render: (_, record) => (
+        <img
+          src={`/api/File/Download?fileId=${record.icon}`}
+          alt=""
+          style={{
+            width: 30,
+            height: 30,
+            borderRadius: "50%",
+          }}
+        />
+      ),
     },
-    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',
+    {
+      title: "应用名称",
+      dataIndex: "name",
+      copyable: true,
+      ellipsis: true,
+    },
+    {
+      title: "作者",
+      dataIndex: "createByName",
+      ellipsis: true,
+      search: false,
+    },
+    // {
+    //   title: "应用场景",
+    //   dataIndex: "applicationScenarios",
+    //   ellipsis: true,
+    //   search: false,
+    //   renderText: (text) => {
+    //     return (JSON.parse(text || '[]')).map((item: string) => (
+    //       <Tag key={item}>{item}</Tag>
+    //     ));
+    //   },
+    // },
+    // {
+    //   title: "应用行业",
+    //   dataIndex: "industries",
+    //   ellipsis: true,
+    //   search: false,
+    //   renderText: (text) => {
+    //     return (JSON.parse(text || '[]')).map((item: string) => (
+    //       <Tag key={item}>{item}</Tag>
+    //     ));
+    //   },
+    // },
+    {
+      disable: true,
+      title: "标签",
+      dataIndex: "tags",
+      search: false,
+    },
+    {
+      title: "上架状态",
+      dataIndex: "isOnMarket",
+      filters: true,
+      onFilter: true,
+      ellipsis: true,
+      valueType: "select",
+      valueEnum: {
+        on: {
+          text: "已上架",
+          status: "Error",
+        },
+        off: {
+          text: "未上架",
+          status: "Success",
+        },
       },
-      closed: {
-        text: '未上架',
-        status: 'Success',
+      render: (_, record) => (
+        <Tag color={record.isOnMarket ? "green" : "red"}>
+          {record.isOnMarket ? "已上架" : "未上架"}
+        </Tag>
+      ),
+    },
+    {
+      title: "使用量",
+      dataIndex: "useNum",
+      search: false,
+    },
+    {
+      title: "价格",
+      dataIndex: "price",
+      search: false,
+      renderText: (text) => {
+        return text ? `¥${text}` : "免费";
       },
     },
-  },
-  {
-    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>();
+    {
+      title: "试用地址",
+      dataIndex: "trialUrl",
+      search: false,
+      renderText: (text) =>
+        text ? (
+          <a target="_blank" rel="noopener noreferrer" href={text}>
+            {text}
+          </a>
+        ) : (
+          "-"
+        ),
+    },
+    {
+      title: "创建时间",
+      dataIndex: "createTime",
+      search: false,
+    },
+    {
+      title: "更新时间",
+      dataIndex: "updateTime",
+      search: false,
+    },
+    {
+      title: "操作",
+      valueType: "option",
+      key: "option",
+      width: 180,
+      render: (text, record, _, action) => [
+        <a key="edit" onClick={() => setEditData(record)}>
+          编辑
+        </a>,
+        <a
+          target="_blank"
+          rel="noopener noreferrer"
+          key="view"
+          onClick={() => handleToDetail(record.id)}
+        >
+          详情
+        </a>,
+        record.isOnMarket ? (
+          <a
+            target="_blank"
+            rel="noopener noreferrer"
+            key="off"
+            onClick={() => handleOffMarket(record.id)}
+          >
+            下架
+          </a>
+        ) : (
+          <a
+            target="_blank"
+            rel="noopener noreferrer"
+            key="on"
+            onClick={() => handleOnMarket(record.id)}
+          >
+            上架
+          </a>
+        ),
+        <Popconfirm
+          key="delete"
+          title="确定删除吗?"
+          onConfirm={() => handleDelete(record.id)}
+        >
+          <a className="text-red-500" target="_blank" rel="noopener noreferrer">
+            删除
+          </a>
+        </Popconfirm>,
+      ],
+    },
+  ];
 
   return (
     <ProTable<AppItem>
@@ -106,35 +219,39 @@ export default () => {
       cardBordered
       request={async (params, sort, filter) => {
         console.log(params, sort, filter);
+        const isOnMarket =
+          params?.isOnMarket === "on"
+            ? 1
+            : params?.isOnMarket === "off"
+              ? 0
+              : undefined;
         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},
-          ]
+            { name: "name", value: params?.name },
+            { name: "isFree", value: params?.isFree },
+            { name: "isOnMarket", value: isOnMarket },
+            { name: "isDel", value: 0 },
+          ],
         });
-        
+        const data = res?.result || {};
         return {
-          data: [],
+          data: data.model || [],
           success: true,
-          total: res.totalCount
-        }
+          total: data.totalCount || 0,
+        };
       }}
       columnsState={{
-        persistenceKey: 'shalu-marketplace',
-        persistenceType: 'localStorage',
+        persistenceKey: "shalu-marketplace",
+        persistenceType: "localStorage",
         defaultValue: {
-          option: { fixed: 'right', disable: true },
+          option: { fixed: "right", disable: true },
         },
       }}
       rowKey="id"
       search={{
-        labelWidth: 'auto',
+        labelWidth: "auto",
       }}
       options={{
         setting: {
@@ -143,10 +260,17 @@ export default () => {
       }}
       pagination={{
         pageSize: 10,
-        onChange: (page) => console.log(page),
       }}
       dateFormatter="string"
-      headerTitle={<AddAppDrawer />}
+      headerTitle={
+        <AddAppDrawer
+          editData={editData}
+          onSuccess={() => {
+            actionRef.current?.reload();
+            setEditData(undefined);
+          }}
+        />
+      }
     />
   );
-}
+};

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

@@ -1,166 +0,0 @@
-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 />}
-    />
-  );
-}

+ 243 - 0
src/pages/management/TemplateTab.tsx

@@ -0,0 +1,243 @@
+import { useRef, useState } from "react";
+import { ActionType, ProColumns } from '@ant-design/pro-components';
+import { ProTable } from "@ant-design/pro-components";
+import { Space, Tag, message, Popconfirm } from 'antd';
+import AddTemplateDrawer from "./AddTemplateDrawer";
+import { GetTemplateList, DeleteTemplate, OnMarketTemplate, OffMarketTemplate } from "@/api/templateStore";
+import { history } from "umi";
+import { MODULE_TEMPLATE_TYPE } from "@/constants";
+
+export default () => {
+  const actionRef = useRef<ActionType>();
+  const [editData, setEditData] = useState<any>();
+  const handleToDetail = (id?: string) => {
+    history.push(`/detail/application/${id}`);
+  };
+
+  const handleOffMarket = async (id?: string) => {
+    if (id) {
+      await OffMarketTemplate({ ids: [id] });
+      actionRef.current?.reload();
+      message.success("下架成功");
+    }
+  };
+
+  const handleOnMarket = async (id?: string) => {
+    if (id) {
+      await OnMarketTemplate({ ids: [id] });
+      actionRef.current?.reload();
+      message.success("上架成功");
+    }
+  };
+
+  const handleDelete = async (id?: string) => {
+    if (id) {
+      await DeleteTemplate({ ids: [id] });
+      actionRef.current?.reload();
+      message.success("删除成功");
+    }
+  };
+
+  const columns: ProColumns<any>[] = [
+    {
+      title: "图标",
+      dataIndex: "icon",
+      search: false,
+      render: (_, record) => (
+        <img
+          src={`/api/File/Download?fileId=${record.icon}`}
+          alt=""
+          style={{
+            width: 30,
+            height: 30,
+            borderRadius: "50%",
+          }}
+        />
+      ),
+    },
+    {
+      title: '模版名称',
+      dataIndex: 'name',
+      copyable: true,
+      ellipsis: true,
+    },
+    {
+      title: '作者',
+      dataIndex: 'createByName',
+      ellipsis: true,
+      search: false,
+    },
+    {
+      title: '模版分类',
+      dataIndex: 'type',
+      copyable: true,
+      ellipsis: true,
+      valueType: "select",
+      fieldProps: {
+        options: MODULE_TEMPLATE_TYPE
+      }
+    },
+    {
+      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: "isOnMarket",
+      filters: true,
+      onFilter: true,
+      ellipsis: true,
+      valueType: "select",
+      valueEnum: {
+        on: {
+          text: "已上架",
+          status: "Error",
+        },
+        off: {
+          text: "未上架",
+          status: "Success",
+        },
+      },
+      render: (_, record) => (
+        <Tag color={record.isOnMarket ? "green" : "red"}>
+          {record.isOnMarket ? "已上架" : "未上架"}
+        </Tag>
+      ),
+    },
+    {
+      title: '使用量',
+      dataIndex: 'useNum',
+      search: false,
+    },
+    {
+      title: "价格",
+      dataIndex: "price",
+      search: false,
+      renderText: (text) => {
+        return text ? `¥${text}` : "免费";
+      },
+    },
+    {
+      title: "更新时间",
+      dataIndex: "createTime",
+      search: false,
+    },
+    {
+      title: "更新时间",
+      dataIndex: "updateTime",
+      search: false,
+    },
+    {
+      title: "操作",
+      valueType: "option",
+      key: "option",
+      width: 180,
+      render: (text, record, _, action) => [
+        <a key="edit" onClick={() => setEditData(record)}>
+          编辑
+        </a>,
+        <a
+          target="_blank"
+          rel="noopener noreferrer"
+          key="view"
+          onClick={() => handleToDetail(record.id)}
+        >
+          详情
+        </a>,
+        record.isOnMarket ? (
+          <a
+            target="_blank"
+            rel="noopener noreferrer"
+            key="off"
+            onClick={() => handleOffMarket(record.id)}
+          >
+            下架
+          </a>
+        ) : (
+          <a
+            target="_blank"
+            rel="noopener noreferrer"
+            key="on"
+            onClick={() => handleOnMarket(record.id)}
+          >
+            上架
+          </a>
+        ),
+        <Popconfirm
+          key="delete"
+          title="确定删除吗?"
+          onConfirm={() => handleDelete(record.id)}
+        >
+          <a className="text-red-500" target="_blank" rel="noopener noreferrer">
+            删除
+          </a>
+        </Popconfirm>,
+      ],
+    },
+  ];
+
+  return (
+    <ProTable
+      columns={columns}
+      actionRef={actionRef}
+      cardBordered
+      request={async (params, sort, filter) => {
+        const isOnMarket = params?.isOnMarket === 'on' ? 1 : params?.isOnMarket === 'off' ? 0 : undefined;
+        const res = await GetTemplateList({
+          currentPage: params.current || 1,
+          pageSize: params.pageSize || 10,
+          filters: [
+            {name: 'name', value: params?.name},
+            {name: 'isFree', value: params?.isFree},
+            {name: 'isOnMarket', value: isOnMarket},
+            {name: 'isDel', value: 0},
+            {name: 'type', value: params?.type}
+          ]
+        });
+
+        const data = res?.result || {};
+        
+        return {
+          success: true,
+          data: data.model || [],
+          total: data.totalCount || 0,
+        }
+      }}
+      columnsState={{
+        persistenceKey: 'shalu-marketplace',
+        persistenceType: 'localStorage',
+        defaultValue: {
+          option: { fixed: 'right', disable: true },
+        },
+      }}
+      rowKey="id"
+      search={{
+        labelWidth: 'auto',
+      }}
+      options={{
+        setting: {
+          listsHeight: 400,
+        },
+      }}
+      pagination={{
+        pageSize: 10,
+      }}
+      dateFormatter="string"
+      headerTitle={<AddTemplateDrawer onSuccess={() => {actionRef.current?.reload()}} editData={editData} />}
+    />
+  );
+}

+ 3 - 4
src/pages/management/index.tsx

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

+ 72 - 21
src/pages/template/index.tsx

@@ -1,15 +1,19 @@
-import { useState } from "react";
+import { useState, useEffect } from "react";
 import ItemCard from "@/components/ItemCard";
 import { APPLICATION_SCENARIOS_OPTIONS } from "@/constants";
-import { history } from "umi";
+import { history, useRequest } from "umi";
+import { GetTemplateList } from "@/api/templateStore";
+import { Input, Spin, Empty } from "antd";
+import { MODULE_TEMPLATE_TYPE } from "@/constants";
+import noDataImg from "@/assets/no-data.svg";
 
-type SceneItem = { 
+type OptionItem = { 
   label: string;
   value: string;
   icon?: JSX.Element;
 }
 
-const scenes: SceneItem[] = [
+const scenes: OptionItem[] = [
   {
     label: "推荐",
     icon: <i className="iconfont icon-tuijian mr-1" />,
@@ -17,21 +21,50 @@ const scenes: SceneItem[] = [
   },
   ...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 categorys: OptionItem[] = [
+  { label: "全部类型", value: "all" },
+  ...MODULE_TEMPLATE_TYPE
+];
 
+export default function Template() {
+  const [search, setSearch] = useState("");
   const [industryFilter, setIndustryFilter] = useState("all");
   const [sceneFilter, setSceneFilter] = useState("recommend");
+  const { data, run, loading } = useRequest(GetTemplateList, {
+    defaultParams: [
+      {
+        currentPage: 1,
+        pageSize: 1000,
+        filters: [
+          { name: "isDel", value: 0 },
+          { name: "isOnMarket", value: 1 },
+        ],
+      },
+    ],
+  });
+
+  useEffect(() => {
+    run({
+      currentPage: 1,
+      pageSize: 1000,
+      filters: [
+        { name: "isDel", value: 0 },
+        { name: "isOnMarket", value: 1 },
+        { name: "name", value: search },
+        {
+          name: "industries",
+          value: industryFilter === "all" ? "" : industryFilter,
+        },
+        {
+          name: "applicationScenarios",
+          value: sceneFilter === "recommend" ? "" : sceneFilter,
+        },
+      ],
+    });
+  }, [industryFilter, sceneFilter, search]);
 
   const handleToAppDetail = (id: string) => {
-    history.push(`/detail/application/${id}`);
+    history.push(`/detail/template/${id}`);
   }
 
   return (
@@ -44,7 +77,7 @@ export default function Template() {
               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>
+              <span>{item.label}</span>
             </li>
           ))}
         </ul>
@@ -55,7 +88,7 @@ export default function Template() {
             探索模块模版
           </div>
           <div className="text-gray-500 text-sm">
-            使用这些模板,可以快速搭建你的系统功能模块。
+            使用这些内容模块,可以快速完善你的系统功能模块。
           </div>
         </div>
         <div className="flex items-center justify-between mt-6 px-12">
@@ -71,14 +104,32 @@ export default function Template() {
               </div>
             ))}
           </div>
+          <div>
+            <Input
+              value={search}
+              onChange={(e) => setSearch(e.target.value)}
+              placeholder="搜索"
+              prefix={<i className="iconfont icon-sousuo" />}
+              allowClear
+            ></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>
+          <Spin spinning={loading}>
+            <nav
+              className="grid content-start shrink-0 gap-4 px-6 sm:px-12"
+              style={{ gridTemplateColumns: "repeat(3,minmax(0,1fr))" }}
+            >
+              {(data?.result?.model || []).map((item: any, index: number) => (
+                <ItemCard data={item} key={index} onClick={handleToAppDetail} />
+              ))}
+            </nav>
+
+            {!data?.result.model.length && !loading && (
+              <Empty description="暂无数据" image={noDataImg} />
+            )}
+          </Spin>
         </div>
       </div>
     </div>

+ 3 - 1
unocss.config.ts

@@ -8,7 +8,8 @@ export function createConfig({strict = true, dev = true} = {}) {
         'primary': '#0e53e2',
         'secondary': '#495464',
         'text-secondary': '#354052',
-        'text-tertiary': '#676f83'
+        'text-tertiary': '#676f83',
+        'text-quaternary': '#1018284d'
       }
     },
     rules: [
@@ -18,6 +19,7 @@ export function createConfig({strict = true, dev = true} = {}) {
       '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]',
+      'system-xs-regular': 'text-12px font-400 leading-16px'
     }
   });
 }