shenwu há 7 meses atrás
pai
commit
4819138d8e
100 ficheiros alterados com 11245 adições e 1237 exclusões
  1. 3 7
      README.md
  2. 3 3
      config/config.ts
  3. 0 1
      config/defaultSettings.ts
  4. 2 17
      config/proxy.ts
  5. 51 38
      config/routes.tsx
  6. 2 0
      package.json
  7. BIN
      public/init.png
  8. 83 64
      src/Hook/useAjax.tsx
  9. 76 0
      src/Hook/useOss.tsx
  10. 10 7
      src/access.ts
  11. 53 50
      src/app.tsx
  12. 212 0
      src/components/CropperImg/index.tsx
  13. 35 33
      src/components/DraggableButton.tsx
  14. 2 2
      src/components/HeaderDropdown/index.tsx
  15. 26 13
      src/components/MenuChange.tsx
  16. 24 20
      src/components/RightContent/AvatarDropdown.tsx
  17. 63 29
      src/components/RightContent/index.tsx
  18. 243 0
      src/components/UploadFile/index.tsx
  19. 1 1
      src/components/index.ts
  20. 257 0
      src/components/readBook/index.tsx
  21. 189 0
      src/components/readText/index.tsx
  22. 3 0
      src/components/uploadImg/index.less
  23. 124 0
      src/components/uploadImg/index.tsx
  24. 1 1
      src/global.less
  25. 5 5
      src/global.tsx
  26. 2 2
      src/locales/zh-CN/menu.ts
  27. 107 30
      src/models/global.tsx
  28. 308 284
      src/pages/User/Login/index.tsx
  29. 2 2
      src/pages/User/Login/login.test.tsx
  30. 129 132
      src/pages/User/Login/style.less
  31. 0 164
      src/pages/Welcome.tsx
  32. 50 0
      src/pages/book/bookManage/author/formConfig.tsx
  33. 125 4
      src/pages/book/bookManage/author/index.tsx
  34. 93 0
      src/pages/book/bookManage/author/tableConfig.tsx
  35. 65 0
      src/pages/book/bookManage/bookTags/formConfig.tsx
  36. 164 4
      src/pages/book/bookManage/bookTags/index.tsx
  37. 174 0
      src/pages/book/bookManage/bookTags/tableConfig.tsx
  38. 51 0
      src/pages/book/bookManage/classifyList/formConfig.tsx
  39. 145 4
      src/pages/book/bookManage/classifyList/index.tsx
  40. 135 0
      src/pages/book/bookManage/classifyList/tableConfig.tsx
  41. 280 0
      src/pages/book/bookManage/longbookList/formConfig.tsx
  42. 338 4
      src/pages/book/bookManage/longbookList/index.tsx
  43. 215 0
      src/pages/book/bookManage/longbookList/item.tsx
  44. 491 0
      src/pages/book/bookManage/longbookList/tableConfig.tsx
  45. 280 0
      src/pages/book/bookManage/shortbookList/formConfig.tsx
  46. 329 4
      src/pages/book/bookManage/shortbookList/index.tsx
  47. 202 0
      src/pages/book/bookManage/shortbookList/item.tsx
  48. 483 0
      src/pages/book/bookManage/shortbookList/tableConfig.tsx
  49. 303 0
      src/pages/distribution/book/longBook/formConfig.tsx
  50. 132 4
      src/pages/distribution/book/longBook/index.tsx
  51. 518 0
      src/pages/distribution/book/longBook/tableConfig.tsx
  52. 303 0
      src/pages/distribution/book/shortBook/formConfig.tsx
  53. 133 4
      src/pages/distribution/book/shortBook/index.tsx
  54. 518 0
      src/pages/distribution/book/shortBook/tableConfig.tsx
  55. 150 0
      src/pages/distribution/distributor/info/formConfig.tsx
  56. 153 4
      src/pages/distribution/distributor/info/index.tsx
  57. 170 0
      src/pages/distribution/distributor/info/item.tsx
  58. 255 0
      src/pages/distribution/distributor/info/tableConfig.tsx
  59. 3 3
      src/pages/distribution/miniprogram/mimiModule/index.tsx
  60. 158 0
      src/pages/distribution/miniprogram/weChatInfo/formConfig.tsx
  61. 210 4
      src/pages/distribution/miniprogram/weChatInfo/index.tsx
  62. 186 0
      src/pages/distribution/miniprogram/weChatInfo/tableConfig.tsx
  63. 46 4
      src/pages/distribution/orderLog/long/index.tsx
  64. 148 0
      src/pages/distribution/orderLog/long/tableConfig.tsx
  65. 46 4
      src/pages/distribution/orderLog/short/index.tsx
  66. 148 0
      src/pages/distribution/orderLog/short/tableConfig.tsx
  67. 41 4
      src/pages/distribution/payLog/long/index.tsx
  68. 108 0
      src/pages/distribution/payLog/long/tableConfig.tsx
  69. 41 4
      src/pages/distribution/payLog/short/index.tsx
  70. 105 0
      src/pages/distribution/payLog/short/tableConfig.tsx
  71. 38 4
      src/pages/distribution/readLog/long/index.tsx
  72. 90 0
      src/pages/distribution/readLog/long/tableConfig.tsx
  73. 38 4
      src/pages/distribution/readLog/short/index.tsx
  74. 88 0
      src/pages/distribution/readLog/short/tableConfig.tsx
  75. 107 4
      src/pages/distribution/system/index.tsx
  76. 67 0
      src/pages/distribution/system/tableConfig.tsx
  77. 5 6
      src/requestErrorConfig.ts
  78. 0 94
      src/services/ant-design-pro/api.ts
  79. 0 10
      src/services/ant-design-pro/index.ts
  80. 0 21
      src/services/ant-design-pro/login.ts
  81. 0 101
      src/services/ant-design-pro/typings.d.ts
  82. 2 1
      src/services/api.ts
  83. 58 0
      src/services/book/author/index.tsx
  84. 82 0
      src/services/book/bookTags/index.tsx
  85. 75 0
      src/services/book/classifyList/index.tsx
  86. 179 0
      src/services/book/longbookList/index.tsx
  87. 183 0
      src/services/book/shortbookList/index.tsx
  88. 92 0
      src/services/distribution/accountInfo/index.tsx
  89. 117 0
      src/services/distribution/info/index.tsx
  90. 64 0
      src/services/distribution/longBook/index.tsx
  91. 80 0
      src/services/distribution/miniprogram/index.tsx
  92. 53 0
      src/services/distribution/orderLog/index.tsx
  93. 33 0
      src/services/distribution/payLog/index.tsx
  94. 33 0
      src/services/distribution/readLog/index.tsx
  95. 60 0
      src/services/distribution/shortBook/index.tsx
  96. 24 0
      src/services/distribution/system/index.tsx
  97. 106 0
      src/services/distribution/weChatInfo/index.tsx
  98. 52 12
      src/services/global.ts
  99. 8 8
      src/services/login/index.tsx
  100. 0 12
      src/services/swagger/index.ts

+ 3 - 7
README.md

@@ -1,9 +1,5 @@
-routes.tsx 配置一套本地路由(重要)
-app.tsx 需要修改的配置
-menu.request 中的 getMenu获取线上路由方法传入对应要获取的后台参数
+routes.tsx 配置一套本地路由(重要) app.tsx 需要修改的配置 menu.request 中的 getMenu 获取线上路由方法传入对应要获取的后台参数
 
-global.tsx
-scriptUrl 为设置阿里icon使用的地址
-MyIcon 为阿里icon 使用 <MyIcon type='icon-yejing' />
+global.tsx scriptUrl 为设置阿里 icon 使用的地址 MyIcon 为阿里 icon 使用 <MyIcon type='icon-yejing' />
 
-icon 以本地路由设置的icon为准,线上路由只用于参考是否显示
+icon 以本地路由设置的 icon 为准,线上路由只用于参考是否显示

+ 3 - 3
config/config.ts

@@ -5,12 +5,12 @@ import defaultSettings from './defaultSettings';
 import proxy from './proxy';
 import routes from './routes';
 const { REACT_APP_ENV = 'dev' } = process.env;
-const assetDir = "static"
-let isBuild = process.env.npm_lifecycle_event?.includes("build")
+const assetDir = 'static';
+let isBuild = process.env.npm_lifecycle_event?.includes('build');
 export default defineConfig({
   // favicons:['../public/logo.svg'],//浏览器tab栏上的logo
   history: {
-    type: 'hash'
+    type: 'hash',
   },
   /**
    * @name 开启 hash 模式

+ 0 - 1
config/defaultSettings.ts

@@ -27,5 +27,4 @@ const Settings: ProLayoutProps & {
   },
 };
 
-
 export default Settings;

+ 2 - 17
config/proxy.ts

@@ -6,28 +6,13 @@
  * https://pro.ant.design/docs/deploy
  */
 
-
- export default {
+export default {
   dev: {
     '/api/': {
-      // target: 'http://test.api.zanxiangwl.com',
-      // target: 'https://game.84game.cn',
       target: 'https://testapi.zanxiangwl.com',
       changeOrigin: true,
       pathRewrite: { '/api': '' },
     },
-    '/erpApi/': {
-      target: 'https://testapi.zanxiangwl.com',
-      // target: 'https://api.zanxiangwl.com',
-      changeOrigin: true,
-      pathRewrite: { '/erpApi': '' },
-    },
-    '/gameApi/': {
-      // target: 'http://test.api.zanxiangwl.com',//服务器
-      target: 'https://game.84game.cn',//服务器
-      changeOrigin: true,
-      pathRewrite: { '^/gameApi/': '' },
-    },
   },
   test: {
     '/api/': {
@@ -43,4 +28,4 @@
       pathRewrite: { '^': '' },
     },
   },
-}
+};

+ 51 - 38
config/routes.tsx

@@ -1,5 +1,4 @@
-
-/**
+/**
  * @name umi 的路由配置
  * @description 只支持 path,component,routes,redirect,wrappers,name,icon 的配置
  * @param path  path 只支持两种占位符配置,第一种是动态参数 :id 的形式,第二种是 * 通配符,通配符只能出现路由字符串的最后。
@@ -14,7 +13,7 @@
 const newMenu = [
   {
     path: '/book',
-    name: "小说中心",
+    name: '小说中心',
     icon: 'icon-xiaoshuo',
     routes: [
       {
@@ -23,34 +22,40 @@ const newMenu = [
       },
       {
         path: '/book/bookManage/author',
-        name: "作者管理",
+        name: '作者管理',
+        icon: 'icon-gaojian-zuozhe',
         component: './book/bookManage/author',
       },
       {
         path: '/book/bookManage/bookTags',
-        name: "标签管理",
+        name: '标签管理',
+        icon: 'icon-biaoqian',
         component: './book/bookManage/bookTags',
       },
       {
         path: '/book/bookManage/classifyList',
-        name: "分类管理",
+        name: '分类管理',
+        icon: 'icon-fenlei',
         component: './book/bookManage/classifyList',
       },
       {
         path: '/book/bookManage/longbookList',
-        name: "长篇小说",
+        name: '长篇小说',
+        icon: 'icon-changpianxiaoshuo',
         component: './book/bookManage/longbookList',
       },
       {
         path: '/book/bookManage/shortbookList',
-        name: "短篇小说",
+        name: '短篇小说',
+        icon: 'icon-changpianxiaoshuo',
         component: './book/bookManage/shortbookList',
       },
     ],
   },
   {
     path: '/distribution',
-    name: "分销中心",
+    name: '分销中心',
+    icon: 'icon-fenxiao',
     routes: [
       {
         path: '/distribution',
@@ -58,12 +63,14 @@ const newMenu = [
       },
       {
         path: '/distribution/distributor/info',
-        name: "分销商信息",
+        name: '分销商信息',
+        icon: 'icon-fenxiaoshangxiaoxi',
         component: './distribution/distributor/info',
       },
       {
         path: '/distribution/miniprogram',
-        name: "小程序",
+        name: '小程序',
+        icon: 'icon-xiaochengxu',
         routes: [
           {
             path: '/distribution/miniprogram',
@@ -71,104 +78,109 @@ const newMenu = [
           },
           {
             path: '/distribution/miniprogram/weChatInfo',
-            name: "小程序信息",
+            name: '小程序信息',
             component: './distribution/miniprogram/weChatInfo',
           },
           {
             path: '/distribution/miniprogram/mimiModule',
-            name: "小程序组件配置",
+            name: '小程序组件配置',
             component: './distribution/miniprogram/mimiModule',
           },
-        ]
+        ],
       },
       {
         path: '/distribution/book',
-        name: "小说信息",
+        name: '小说信息',
+        icon: 'icon-xiaoshuoxinxi',
         routes: [
           {
             path: '/distribution/book',
             redirect: '/distribution/book//longBook',
           },
           {
-            path: '/distribution/book//longBook',
-            name: "长篇小说",
+            path: '/distribution/book/longBook',
+            name: '长篇小说',
             component: './distribution/book/longBook',
           },
           {
             path: '/distribution/book/shortBook',
-            name: "短篇小说",
+            name: '短篇小说',
             component: './distribution/book/shortBook',
           },
-        ]
+        ],
       },
       {
         path: '/distribution/readLog',
-        name: "阅读记录",
+        name: '阅读记录',
+        icon: 'icon-yuedu-2',
         routes: [
           {
             path: '/distribution/readLog',
             redirect: '/distribution/readLog/long',
           },
           {
-            path: '/distribution/readLog//long',
-            name: "长篇小说",
+            path: '/distribution/readLog/long',
+            name: '长篇小说',
             component: './distribution/readLog/long',
           },
           {
             path: '/distribution/readLog/short',
-            name: "短篇小说",
+            name: '短篇小说',
             component: './distribution/readLog/short',
           },
-        ]
+        ],
       },
       {
         path: '/distribution/orderLog',
-        name: "订单记录",
+        name: '订单记录',
+        icon: 'icon-xiaofei',
         routes: [
           {
             path: '/distribution/orderLog',
             redirect: '/distribution/orderLog/long',
           },
           {
-            path: '/distribution/orderLog//long',
-            name: "长篇小说",
+            path: '/distribution/orderLog/long',
+            name: '长篇小说',
             component: './distribution/orderLog/long',
           },
           {
             path: '/distribution/orderLog/short',
-            name: "短篇小说",
+            name: '短篇小说',
             component: './distribution/orderLog/short',
           },
-        ]
+        ],
       },
       {
         path: '/distribution/payLog',
-        name: "消费记录",
+        name: '书币记录',
+        icon: 'icon-shubi',
         routes: [
           {
             path: '/distribution/payLog',
             redirect: '/distribution/payLog/long',
           },
           {
-            path: '/distribution/payLog//long',
-            name: "长篇小说",
+            path: '/distribution/payLog/long',
+            name: '长篇小说',
             component: './distribution/payLog/long',
           },
           {
             path: '/distribution/payLog/short',
-            name: "短篇小说",
+            name: '短篇小说',
             component: './distribution/payLog/short',
           },
-        ]
+        ],
       },
       {
         path: '/distribution/system',
-        name: "系统信息",
+        name: '系统信息',
+        icon: 'icon-xitong',
         component: './distribution/system',
       },
     ],
   },
-]
+];
 export default [
   {
     path: '/user',
@@ -181,7 +193,8 @@ export default [
       },
     ],
   },
-  {//默认进入后打开哪个页面
+  {
+    //默认进入后打开哪个页面
     path: '/',
     redirect: '/book',
   },
@@ -190,5 +203,5 @@ export default [
     layout: false,
     component: './404',
   },
-  ...newMenu
+  ...newMenu,
 ];

+ 2 - 0
package.json

@@ -50,6 +50,7 @@
   "dependencies": {
     "@ant-design/icons": "^4.8.1",
     "@ant-design/pro-components": "^2.6.48",
+    "@types/react-cropper": "^1.3.7",
     "@umijs/route-utils": "^2.2.2",
     "antd": "^5.13.2",
     "antd-style": "^3.6.1",
@@ -61,6 +62,7 @@
     "rc-menu": "^9.12.4",
     "rc-util": "^5.38.1",
     "react": "^18.2.0",
+    "react-cropper": "^2.3.3",
     "react-dom": "^18.2.0",
     "react-helmet-async": "^1.3.0"
   },

BIN
public/init.png


+ 83 - 64
src/Hook/useAjax.tsx

@@ -1,18 +1,18 @@
-import { message } from 'antd'
-import { useState } from 'react'
+import { message } from 'antd';
+import { useState } from 'react';
 export interface AjaxPromise {
-    /**请求*/
-    run: (params?: any) => Promise<any>,
-    /**执行上次的请求*/
-    refresh: () => Promise<any>,
-    /** 修改data */
-    mutate: (data: any) => void
-    /**清空data*/
-    initData: () => void,
-    /**loding状态*/
-    loading: boolean,
-    /**data数据*/
-    data: any,
+  /**请求*/
+  run: (params?: any) => Promise<any>;
+  /**执行上次的请求*/
+  refresh: () => Promise<any>;
+  /** 修改data */
+  mutate: (data: any) => void;
+  /**清空data*/
+  initData: () => void;
+  /**loding状态*/
+  loading: boolean;
+  /**data数据*/
+  data: any;
 }
 /**
  * ajax封装方法 hook 方法不要使用在非页面中
@@ -22,56 +22,75 @@ export interface AjaxPromise {
  * @returns data 请求结果数据
  * @returns initData 初始data
  * */
-export function useAjax(fnc: (params?: any) => Promise<any>, options?: { type: 'table' }): AjaxPromise {
-    // const {message}= App.useApp()
-    const [loading, setLoding] = useState(false)//状态
-    const [data, setData] = useState<any>()//数据
-    const [oldParams, setOldParasm] = useState()
-    async function run(params?: any) {//请求
-        setLoding(() => true)//开启加载
-        setOldParasm(() => params)//存放本次请求参数
-        // 表单接口处理参数
+export function useAjax(
+  fnc: (params?: any) => Promise<any>,
+  options?: { type: 'table' | 'noPage' },
+): AjaxPromise {
+  // const {message}= App.useApp()
+  const [loading, setLoding] = useState(false); //状态
+  const [data, setData] = useState<any>(); //数据
+  const [oldParams, setOldParasm] = useState();
+  async function run(params?: any) {
+    //请求
+    setLoding(() => true); //开启加载
+    setOldParasm(() => params); //存放本次请求参数
+    // 表单接口处理参数
+    if (options?.type === 'table') {
+      params.pageNum = params.current;
+      delete params.current;
+    }
+    return fnc(params)
+      .then((res) => {
+        //开启请求
+        setLoding(() => false); //关闭请求加载
+        setData(() => res); //设置data
+        if (res?.code === 500) {
+          message.error(res?.msg);
+          return res;
+        }
         if (options?.type === 'table') {
-            params.pageNum = params.current
-            delete params.current
+          //表单数据返回
+          return {
+            data: res.data.records,
+            total: res.data.total,
+            current: res.data.current,
+            pageSize: res.data.size,
+            success: res.success,
+          };
         }
-        return fnc(params).then(res => {//开启请求
-            setLoding(() => false)//关闭请求加载
-            setData(() => res)//设置data
-            if (res?.code === 500) {
-                message.error(res?.msg)
-                return res
-            }
-            if (options?.type === 'table') {//表单数据返回
-                return {
-                    data: res.data.records,
-                    total: res.data.total,
-                    current: res.data.current,
-                    pageSize: res.data.size,
-                    success: res.success
-                }
-            }
-            return res//返回data
-        }).catch(err => {
-            console.log(err)
-            setLoding(() => false)//关闭请求加载
-            return err
-        })
-    }
-    async function refresh() {//上次的请求
-        setLoding(() => true)//开启加载
-        return fnc(oldParams).then(res => {//开启请求
-            setLoding(() => false)//关闭请求加载
-            setData(() => res)//设置data
-            return res//返回data
-        })
-    }
-    async function initData() {
-        setData(null)
-    }
-    // 修改保存数据
-    async function mutate(data: any) {
-        setData(data)
-    }
-    return { loading, data, run, refresh, initData, mutate }
+        if (options?.type === 'noPage') {
+          return {
+            current: 1,
+            data: res.data,
+            pageSize: 20,
+            total: res.data.length,
+            success: true,
+          };
+        }
+        return res; //返回data
+      })
+      .catch((err) => {
+        console.log(err);
+        setLoding(() => false); //关闭请求加载
+        return err;
+      });
+  }
+  async function refresh() {
+    //上次的请求
+    setLoding(() => true); //开启加载
+    return fnc(oldParams).then((res) => {
+      //开启请求
+      setLoding(() => false); //关闭请求加载
+      setData(() => res); //设置data
+      return res; //返回data
+    });
+  }
+  async function initData() {
+    setData(null);
+  }
+  // 修改保存数据
+  async function mutate(data: any) {
+    setData(data);
+  }
+  return { loading, data, run, refresh, initData, mutate };
 }

+ 76 - 0
src/Hook/useOss.tsx

@@ -0,0 +1,76 @@
+import { getOssSecretKeyApi } from '@/services/global';
+import { request } from '@umijs/max';
+import { message } from 'antd';
+import { RcFile } from 'antd/es/upload';
+import { useState } from 'react';
+
+export interface OssPromise {
+  /**上传*/
+  run: (file: RcFile, fileName?: string) => Promise<any>;
+  /**loding状态*/
+  loading: boolean;
+}
+export function useOss(isLoading?: boolean): OssPromise {
+  /**********************************/
+  const [loading, setLoding] = useState(false); //状态
+  /**********************************/
+
+  async function run(file: RcFile, fileName?: string) {
+    //请求
+    let hide: any;
+    if (isLoading) {
+      hide = message.loading('上传中...', 0);
+    }
+    setLoding(true);
+    let name = fileName || file.name.split('.')[0];
+    let suffix = file.type;
+    if (!file.type) {
+      suffix = 'audio/amr';
+    }
+    let res = await getOssSecretKeyApi({ type: suffix }).catch((error) => {
+      message.error(error?.message);
+      setLoding(false);
+      if (isLoading && hide) {
+        hide();
+      }
+    });
+    let msg: string = '';
+    if (res?.data) {
+      let ossData = res.data;
+      let formData = new FormData();
+      Object.keys(ossData).forEach((key: string) => {
+        if (key !== 'ossUrl') {
+          formData.append(key, ossData[key]);
+        }
+      });
+      formData.append('file', file);
+      let data = await request(ossData?.ossUrl, { method: 'POST', data: formData }).catch(
+        (error) => {
+          console.error(error);
+          message.error(error);
+        },
+      );
+      if (data) {
+        setLoding(false);
+        if (isLoading && hide) {
+          hide();
+          message.success('上传成功');
+        }
+        return data;
+      } else {
+        msg = '上传文件到OSSkey失败';
+        message.error('上传文件到OSSkey失败');
+      }
+    } else {
+      msg = '获取OSSkey失败';
+      message.error('获取OSSkey失败');
+    }
+    setLoding(false);
+    if (isLoading && hide) {
+      hide();
+    }
+    return { code: 500, data: null, msg };
+  }
+
+  return { run, loading };
+}

+ 10 - 7
src/access.ts

@@ -1,13 +1,16 @@
 /**
  * @see https://umijs.org/docs/max/access#access
  * */
-export default function access(initialState: { menuType: "distributor" | "miniApp",selectApp:{id:number} }) {
+export default function access(initialState: {
+  menuType: 'distributor' | 'miniApp';
+  selectApp: { id: number };
+}) {
   return {
-    isShow: (route: { name: string; }) => {
-      return true
-    },//路由名称包含menuType才显示
-    isHide:()=>{
-      return false
-    }
+    isShow: (route: { name: string }) => {
+      return true;
+    }, //路由名称包含menuType才显示
+    isHide: () => {
+      return false;
+    },
   };
 }

+ 53 - 50
src/app.tsx

@@ -1,20 +1,18 @@
-import { Footer, Question, AvatarDropdown, AvatarName } from '@/components';
-import type { Settings as LayoutSettings, MenuDataItem } from '@ant-design/pro-components';
-import { getMenuData, SettingDrawer } from '@ant-design/pro-components';
+import { AvatarDropdown, AvatarName, Question } from '@/components';
+import type { Settings as LayoutSettings } from '@ant-design/pro-components';
+import { SettingDrawer } from '@ant-design/pro-components';
 import type { RequestConfig, RunTimeLayoutConfig } from '@umijs/max';
 import { history, useModel } from '@umijs/max';
+import { message } from 'antd';
 import defaultSettings from '../config/defaultSettings';
+import { scriptUrl } from './global';
 import { errorConfig, ResponseStructure } from './requestErrorConfig';
-import React from 'react';
-import { getUserInfo } from './services/login';
-import { message, Space } from 'antd';
-import { MyIcon, scriptUrl } from './global';
 import { getMenu } from './services/global';
+import { getUserInfo } from './services/login';
 import { flattenRoutes } from './utils/generateNewLocalMenu';
 const isDev = process.env.NODE_ENV === 'development';
 const loginPath = '/user/login';
 
-
 /**
  * @see  https://umijs.org/zh-CN/plugins/plugin-initial-state
  * */
@@ -22,16 +20,16 @@ export async function getInitialState(): Promise<{
   settings?: Partial<LayoutSettings>;
   currentUser?: any;
   loading?: boolean;
-  menuType?: "distributor" | "miniApp",
-  navTheme?: "2" | "3",
-  token?: any,
-  menu?: any
+  menuType?: 'distributor' | 'miniApp';
+  navTheme?: '2' | '3';
+  token?: any;
+  menu?: any;
 }> {
   // 如果不是登录页面,执行
   const { location } = history;
-  let menuType = sessionStorage.getItem("menuType") as ("distributor" | "miniApp") || 'distributor';
-  let navTheme: any = localStorage.getItem("navTheme") || '2';//主题色 2白 3黑
-  if (location.pathname !== loginPath && localStorage.getItem("Admin-Token")) {
+  let menuType = (sessionStorage.getItem('menuType') as 'distributor' | 'miniApp') || 'distributor';
+  let navTheme: any = localStorage.getItem('navTheme') || '2'; //主题色 2白 3黑
+  if (location.pathname !== loginPath && localStorage.getItem('Admin-Token')) {
     const res = await getUserInfo();
     if (res.data) {
       return {
@@ -44,8 +42,8 @@ export async function getInitialState(): Promise<{
     }
   }
   return {
-    token: "",
-    menuType: "distributor",
+    token: '',
+    menuType: 'distributor',
     navTheme,
     settings: defaultSettings as Partial<LayoutSettings>,
   };
@@ -54,24 +52,28 @@ export async function getInitialState(): Promise<{
 export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => {
   return {
     // 动态管理菜单
-    splitMenus: true,//切割菜单
+    splitMenus: true, //切割菜单
     menu: {
-      locale: false,//关闭国际化
-      params: {//token变化重新获取
-        token: initialState?.token
+      locale: false, //关闭国际化
+      params: {
+        //token变化重新获取
+        token: initialState?.token,
+      },
+      request: async () => {
+        //需要在routes.tsx中配置一份完整的路由
+        let menu = await getMenu(['book']); //线上获取路由
+        let flatMenu = flattenRoutes(menu);
+        setInitialState({ ...initialState, menu: flatMenu });
+        history.push(flatMenu[flatMenu['/'].redirect].redirect); //获取到本地路由后跳转到有权限的页面
+        return menu;
       },
-      request: async () => {//需要在routes.tsx中配置一份完整的路由
-        let menu = await getMenu(['book'])//线上获取路由
-        let flatMenu = flattenRoutes(menu)
-        setInitialState({ ...initialState, menu: flatMenu, })
-        history.push(flatMenu[flatMenu['/'].redirect].redirect);//获取到本地路由后跳转到有权限的页面
-        return menu
-      }
     },
     iconfontUrl: scriptUrl,
     actionsRender: () => [<Question key="doc" />],
     avatarProps: {
-      src: initialState?.currentUser?.avatar || "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
+      src:
+        initialState?.currentUser?.avatar ||
+        'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
       title: <AvatarName />,
       render: (_, avatarChildren) => {
         return <AvatarDropdown>{avatarChildren}</AvatarDropdown>;
@@ -81,10 +83,12 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
       content: initialState?.currentUser?.name,
     },
     // footerRender: () => <Footer />,//页脚
-    onPageChange: (location: any) => {//页面发生变化触发
-      hasHandledCode300 = false
-      let obj = initialState?.menu || {}
-      if (obj[location.pathname] && obj[location.pathname].redirect) {//强制调整到正确的路由地址
+    onPageChange: (location: any) => {
+      //页面发生变化触发
+      hasHandledCode300 = false;
+      let obj = initialState?.menu || {};
+      if (obj[location.pathname] && obj[location.pathname].redirect) {
+        //强制调整到正确的路由地址
         history.push(obj[location.pathname].redirect);
       }
       // // 在小程序页面禁止用户点击回退按钮返回分享页面
@@ -99,7 +103,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
       // }
       // // 如果没有登录,重定向到 login
 
-      if (!localStorage.getItem("Admin-Token") && location?.pathname !== loginPath) {
+      if (!localStorage.getItem('Admin-Token') && location?.pathname !== loginPath) {
         history.push(loginPath);
       }
     },
@@ -141,13 +145,13 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
     // 增加一个 loading 的状态
     childrenRender: (children: any) => {
       // if (initialState?.loading) return <PageLoading />;
-      // const { init, state } = useModel('global', (ret) => ({
-      //   // init: ret.init,
-      //   state: ret.state
-      // }));
-      // if (!state?.enumList && localStorage.getItem("Admin-Token")) {
-      //   init()//初始全局
-      // }
+      const { init, state } = useModel('global', (ret) => ({
+        init: ret.init,
+        state: ret.state,
+      }));
+      if (!state?.enumList && localStorage.getItem('Admin-Token')) {
+        init(); //初始全局
+      }
       return (
         <>
           {children}
@@ -159,9 +163,9 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
               onSettingChange={(settings) => {
                 setInitialState((preInitialState) => ({
                   ...preInitialState,
-                  menuType: preInitialState?.menuType || "distributor",
+                  menuType: preInitialState?.menuType || 'distributor',
                   settings,
-                  token: preInitialState?.token || "",
+                  token: preInitialState?.token || '',
                   // menu: preInitialState?.menu || [],
                 }));
               }}
@@ -171,7 +175,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
       );
     },
     ...initialState?.settings,
-    navTheme: initialState?.navTheme === '2' ? "light" : "realDark",
+    navTheme: initialState?.navTheme === '2' ? 'light' : 'realDark',
     // title:<span style={{fontSize:24,fontFamily:'cursive'}}>{defaultSettings?.title}</span>
     // title:initialState?.selectApp ? defaultSettings?.title + '--'+initialState?.selectApp?.appName : defaultSettings?.title //标题修改
   };
@@ -191,14 +195,14 @@ export const request: RequestConfig = {
       // 拦截响应数据,进行个性化处理
       const { data } = response as unknown as ResponseStructure;
       switch (data.code) {
-        case 300:
+        case 310:
           if (!hasHandledCode300) {
             hasHandledCode300 = true; // 设置标志位,表示已经处理过
             message.error(data.msg);
-            localStorage.removeItem("Admin-Token");
-            sessionStorage.removeItem("menuType");
-            sessionStorage.removeItem("selectApp");
-            history.push(loginPath)
+            localStorage.removeItem('Admin-Token');
+            sessionStorage.removeItem('menuType');
+            sessionStorage.removeItem('selectApp');
+            history.push(loginPath);
           }
           break;
         // case 500:
@@ -206,7 +210,6 @@ export const request: RequestConfig = {
         //   break
       }
 
-
       return response;
     },
   ],

+ 212 - 0
src/components/CropperImg/index.tsx

@@ -0,0 +1,212 @@
+import { dataURLtoFile } from '@/utils/compress';
+import { useDebounce } from 'ahooks';
+import { Button, Col, Form, InputNumber, Modal, Row } from 'antd';
+import { RcFile } from 'antd/lib/upload';
+import 'cropperjs/dist/cropper.css';
+import React, { useEffect, useRef, useState } from 'react';
+import Cropper from 'react-cropper';
+
+interface Props {
+  size?: { width: number; height: number };
+  isEdit?: boolean;
+  file?: RcFile;
+  visible?: boolean;
+  onChange?: (fileList: any[], file: any) => void;
+  onClose?: () => void;
+}
+
+const CropperImg: React.FC<Props> = (props) => {
+  const { size, isEdit, file, visible, onChange, onClose } = props;
+  const [image, setImage] = useState(''); // 初始图片
+  const cropperRef = useRef<any>(null); // 使用 useRef 来获取实例
+  const [dragMode] = useState<'none' | 'crop' | 'move'>('none');
+  const [imgSize, setImageSize] = useState<{ width: number; height: number }>({
+    width: 0,
+    height: 0,
+  });
+  const [detail, setDetail] = useState<{ width: number; height: number; rotate: number }>({
+    width: 0,
+    height: 0,
+    rotate: 0,
+  }); // 设置数据
+
+  /** 获取宽高 image */
+  useEffect(() => {
+    if (file) {
+      let img: any = new Image();
+      let _URL = window.URL || window.webkitURL;
+      img.onload = function (e: any) {
+        const reader = new FileReader();
+        if (!isEdit) {
+          // 可更改
+          let sizeWidth = size?.width || this.width,
+            sizeHeight = size?.height || this.height;
+          let width = sizeWidth > this.width ? this.width : sizeWidth;
+          let height = sizeHeight > this.height ? this.height : sizeHeight;
+          setImageSize({ width: width, height: height });
+          setDetail({ ...detail, width: width, height: height });
+        } else {
+          // 不可更改
+          setImageSize({ width: this.width, height: this.height });
+          if (size?.width && size?.height) {
+            setDetail({ ...detail, width: size?.width, height: size?.height });
+          } else {
+            setDetail({ ...detail, width: this.width, height: this.height });
+          }
+        }
+
+        reader.onload = async () => {
+          setImage(reader.result as any);
+        };
+        reader.readAsDataURL(file);
+      };
+      img.src = _URL.createObjectURL(file);
+    }
+  }, [size, isEdit]);
+
+  const debouncedValue = useDebounce(detail, { wait: 500 });
+
+  /** 数值变化重置实例 */
+  useEffect(() => {
+    if (cropperRef.current && debouncedValue?.width) {
+      console.log(cropperRef.current?.cropper, debouncedValue);
+      cropperRef.current?.cropper.setData(debouncedValue);
+    }
+  }, [debouncedValue]);
+
+  /** 初始化 */
+  useEffect(() => {
+    if (visible && cropperRef.current) {
+      setTimeout(() => {
+        cropperRef.current?.cropper.reset();
+      }, 200);
+    }
+  }, [visible]);
+
+  /** 关闭 */
+  const cancel = () => {
+    onClose && onClose();
+    setImage('');
+  };
+
+  const getCropData = async () => {
+    if (cropperRef.current) {
+      let newFile = await dataURLtoFile(
+        cropperRef.current?.cropper.getCroppedCanvas().toDataURL('image/jpeg'),
+        file?.name.split('.')[0] + '.jpg',
+      );
+      onChange &&
+        onChange(
+          [
+            {
+              lastModified: newFile.lastModified,
+              name: newFile.name,
+              percent: 0,
+              size: newFile.size,
+              thumbUrl: cropperRef.current?.cropper.getCroppedCanvas().toDataURL('image/jpeg'),
+              type: newFile.type,
+              originFileObj: newFile,
+            },
+          ],
+          newFile,
+        );
+      setImage('');
+    }
+  };
+
+  return (
+    <Modal
+      open={visible}
+      onCancel={cancel}
+      width={1000}
+      footer={
+        <div style={{ display: 'flex', justifyContent: 'center' }}>
+          <Button onClick={cancel}>取消</Button>
+          <Button type="primary" onClick={getCropData}>
+            确定
+          </Button>
+        </div>
+      }
+    >
+      <div style={{ width: '100%' }}>
+        <Row>
+          <Col span={16}>
+            <Cropper
+              ref={cropperRef} // 绑定 ref
+              style={{
+                height: 450,
+                width: 600,
+                backgroundImage: `url(${require('../../../public/init.png')})`,
+                border: '1px solid #efefef',
+              }}
+              initialAspectRatio={1}
+              preview=".img-preview"
+              src={image}
+              viewMode={1}
+              dragMode={dragMode}
+              guides={true}
+              rotatable={true}
+              minCropBoxHeight={10}
+              minCropBoxWidth={10}
+              background={false}
+              responsive={true}
+              autoCropArea={1}
+              cropBoxResizable={false}
+              zoomable={false}
+              movable={false}
+              // onInitialized={(instance: any) => {
+              //     console.log('instance--->', instance);
+              //     cropperRef.current = instance;
+              //     instance.zoomTo(0.6);
+              // }}
+              crop={() => {}}
+            />
+          </Col>
+          <Col span={8}>
+            <Row gutter={[10, 10]}>
+              <Col span={24} style={{ height: 275 }}>
+                <div className="box" style={{ width: '100%', float: 'right', overflow: 'hidden' }}>
+                  <h1 style={{ marginTop: 0 }}>预览</h1>
+                  <div
+                    className="img-preview"
+                    style={{ width: '100%', float: 'left', height: '200px', overflow: 'hidden' }}
+                  />
+                </div>
+              </Col>
+              <Col>
+                <Form>
+                  <Form.Item label="裁剪框宽" style={{ marginBottom: 10 }}>
+                    <InputNumber
+                      style={{ width: 200 }}
+                      value={detail.width}
+                      min={1}
+                      max={imgSize.width}
+                      disabled={isEdit}
+                      onChange={(e) => {
+                        setDetail({ ...detail, width: e || 0 });
+                      }}
+                    />
+                  </Form.Item>
+                  <Form.Item label="裁剪框高" style={{ marginBottom: 0 }}>
+                    <InputNumber
+                      style={{ width: 200 }}
+                      value={detail.height}
+                      min={1}
+                      max={imgSize.height}
+                      disabled={isEdit}
+                      onChange={(e) => {
+                        setDetail({ ...detail, height: e || 0 });
+                      }}
+                    />
+                  </Form.Item>
+                </Form>
+              </Col>
+            </Row>
+          </Col>
+        </Row>
+      </div>
+    </Modal>
+  );
+};
+
+export default React.memo(CropperImg);

+ 35 - 33
src/components/DraggableButton.tsx

@@ -1,33 +1,34 @@
-import React, { useState, useImperativeHandle, forwardRef } from "react";
+import React, { forwardRef, useImperativeHandle, useState } from 'react';
 
 // DraggableButton 组件
-const DraggableButton = forwardRef<HTMLDivElement, { children: React.ReactNode }>(({ children }, ref) => {
-    let homePos = localStorage.getItem("homePos");
+const DraggableButton = forwardRef<HTMLDivElement, { children: React.ReactNode }>(
+  ({ children }, ref) => {
+    let homePos = localStorage.getItem('homePos');
     let initPos = homePos ? JSON.parse(homePos) : { right: 25, top: 100 };
     const [position, setPosition] = useState(initPos);
     const [offset, setOffset] = useState({ x: 0, y: 0 });
 
     const handleDragStart = (e: React.DragEvent) => {
-        const rect = e.currentTarget.getBoundingClientRect();
-        setOffset({
-            x: e.clientX - rect.left,
-            y: e.clientY - rect.top
-        });
+      const rect = e.currentTarget.getBoundingClientRect();
+      setOffset({
+        x: e.clientX - rect.left,
+        y: e.clientY - rect.top,
+      });
     };
 
     const handleDrag = (e: React.DragEvent | any) => {
-        if (e.clientX === 0 && e.clientY === 0) return; // 忽略拖动结束时的最后一个事件
+      if (e.clientX === 0 && e.clientY === 0) return; // 忽略拖动结束时的最后一个事件
 
-        const newRight = window.innerWidth - (e.clientX - offset.x + e.currentTarget.offsetWidth);
-        const newTop = e.clientY - offset.y;
-        setPosition({ right: newRight, top: newTop });
+      const newRight = window.innerWidth - (e.clientX - offset.x + e.currentTarget.offsetWidth);
+      const newTop = e.clientY - offset.y;
+      setPosition({ right: newRight, top: newTop });
     };
 
     const handleDragEnd = (e: React.DragEvent | any) => {
-        const newRight = window.innerWidth - (e.clientX - offset.x + e.currentTarget.offsetWidth);
-        const newTop = e.clientY - offset.y;
-        setPosition({ right: newRight, top: newTop });
-        localStorage.setItem("homePos", JSON.stringify({ right: newRight, top: newTop }));
+      const newRight = window.innerWidth - (e.clientX - offset.x + e.currentTarget.offsetWidth);
+      const newTop = e.clientY - offset.y;
+      setPosition({ right: newRight, top: newTop });
+      localStorage.setItem('homePos', JSON.stringify({ right: newRight, top: newTop }));
     };
 
     // 使用 useImperativeHandle 将 DOM 节点暴露给父组件
@@ -36,23 +37,24 @@ const DraggableButton = forwardRef<HTMLDivElement, { children: React.ReactNode }
     const divRef = React.useRef<HTMLDivElement>(null);
 
     return (
-        <div
-            ref={divRef}
-            draggable
-            onDragStart={handleDragStart}
-            onDrag={handleDrag}
-            onDragEnd={handleDragEnd}
-            style={{
-                position: 'fixed',
-                right: position.right,
-                top: position.top,
-                cursor: 'move',
-                zIndex: 1000
-            }}
-        >
-            {children}
-        </div>
+      <div
+        ref={divRef}
+        draggable
+        onDragStart={handleDragStart}
+        onDrag={handleDrag}
+        onDragEnd={handleDragEnd}
+        style={{
+          position: 'fixed',
+          right: position.right,
+          top: position.top,
+          cursor: 'move',
+          zIndex: 1000,
+        }}
+      >
+        {children}
+      </div>
     );
-});
+  },
+);
 
 export default DraggableButton;

+ 2 - 2
src/components/HeaderDropdown/index.tsx

@@ -1,8 +1,8 @@
 import { Dropdown } from 'antd';
-import type { DropDownProps } from 'antd/es/dropdown';
-import React from 'react';
 import { createStyles } from 'antd-style';
+import type { DropDownProps } from 'antd/es/dropdown';
 import classNames from 'classnames';
+import React from 'react';
 
 const useStyles = createStyles(({ token }) => {
   return {

+ 26 - 13
src/components/MenuChange.tsx

@@ -1,15 +1,28 @@
-import { useModel, history } from "@umijs/max"
-type Props = { menuType: "distributor" | "miniApp", children: any, data: { appId: string, id: string, appName: string, appType: number,appCategory:1|2 } | null }
+import { history, useModel } from '@umijs/max';
+type Props = {
+  menuType: 'distributor' | 'miniApp';
+  children: any;
+  data: { appId: string; id: string; appName: string; appType: number; appCategory: 1 | 2 } | null;
+};
 //切换菜单展示
 const MenuChange: React.FC<Props> = (props) => {
-    let { menuType, children, data } = props
-    let { setInitialState, initialState } = useModel("@@initialState")
-    const change = () => {
-        setInitialState({ ...initialState, menuType, token: initialState?.token || "", selectApp: data })
-        sessionStorage.setItem("menuType", menuType)
-        sessionStorage.setItem("selectApp", JSON.stringify(data))
-        history.push(`/${menuType}`);
-    }
-    return <span onClick={change} style={{ display: 'inline-block', width: '100%' }}>{children}</span>
-}
-export default MenuChange
+  let { menuType, children, data } = props;
+  let { setInitialState, initialState } = useModel('@@initialState');
+  const change = () => {
+    setInitialState({
+      ...initialState,
+      menuType,
+      token: initialState?.token || '',
+      selectApp: data,
+    });
+    sessionStorage.setItem('menuType', menuType);
+    sessionStorage.setItem('selectApp', JSON.stringify(data));
+    history.push(`/${menuType}`);
+  };
+  return (
+    <span onClick={change} style={{ display: 'inline-block', width: '100%' }}>
+      {children}
+    </span>
+  );
+};
+export default MenuChange;

+ 24 - 20
src/components/RightContent/AvatarDropdown.tsx

@@ -6,7 +6,6 @@ import type { MenuInfo } from 'rc-menu/lib/interface';
 import React, { useCallback } from 'react';
 import { flushSync } from 'react-dom';
 import HeaderDropdown from '../HeaderDropdown';
-import { logOut } from '@/services/login';
 
 export type GlobalHeaderRightProps = {
   menu?: boolean;
@@ -47,11 +46,11 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu, childre
     const urlParams = new URL(window.location.href).searchParams;
     /** 此方法会跳转到 redirect 参数所在的位置 */
     // await logOut()
-    localStorage.removeItem("Admin-Token")
-    sessionStorage.removeItem("menuType");
-    sessionStorage.removeItem("selectApp");
+    localStorage.removeItem('Admin-Token');
+    sessionStorage.removeItem('menuType');
+    sessionStorage.removeItem('selectApp');
     // history.push('/user/login')
-    window.location.href = origin
+    window.location.href = origin;
   };
   const { styles } = useStyles();
 
@@ -62,7 +61,12 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu, childre
       const { key } = event;
       if (key === 'logout') {
         flushSync(() => {
-          setInitialState((s) => ({ ...s, menuType: s?.menuType || 'distributor', currentUser: undefined, token: "" }));
+          setInitialState((s) => ({
+            ...s,
+            menuType: s?.menuType || 'distributor',
+            currentUser: undefined,
+            token: '',
+          }));
         });
         loginOut();
         return;
@@ -97,20 +101,20 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu, childre
   const menuItems = [
     ...(menu
       ? [
-        {
-          key: 'center',
-          icon: <UserOutlined />,
-          label: '个人中心',
-        },
-        {
-          key: 'settings',
-          icon: <SettingOutlined />,
-          label: '个人设置',
-        },
-        {
-          type: 'divider' as const,
-        },
-      ]
+          {
+            key: 'center',
+            icon: <UserOutlined />,
+            label: '个人中心',
+          },
+          {
+            key: 'settings',
+            icon: <SettingOutlined />,
+            label: '个人设置',
+          },
+          {
+            type: 'divider' as const,
+          },
+        ]
       : []),
     {
       key: 'logout',

+ 63 - 29
src/components/RightContent/index.tsx

@@ -1,10 +1,10 @@
-import { HomeOutlined, QuestionCircleOutlined } from '@ant-design/icons';
+import { MyIcon } from '@/global';
+import { HomeOutlined } from '@ant-design/icons';
 import { SelectLang as UmiSelectLang, useModel } from '@umijs/max';
-import React, { useEffect, useRef, useState } from 'react';
-import MenuChange from '../MenuChange';
 import { Button, Dropdown, MenuProps, Space, Tour } from 'antd';
+import { useEffect, useRef, useState } from 'react';
 import DraggableButton from '../DraggableButton';
-import { MyIcon } from '@/global';
+import MenuChange from '../MenuChange';
 
 export type SiderTheme = 'light' | 'dark';
 
@@ -23,43 +23,77 @@ export const Question = () => {
   const ref = useRef(null);
   const [open, setOpen] = useState<boolean>(false);
   useEffect(() => {
-    let isOne = localStorage.getItem("isOne")
+    let isOne = localStorage.getItem('isOne');
     if (!isOne && initialState?.menuType === 'miniApp' && !open) {
-      console.log("111111")
-      setOpen(true)
+      console.log('111111');
+      setOpen(true);
     }
-  }, [initialState?.menuType,open])
+  }, [initialState?.menuType, open]);
   const items: MenuProps['items'] = [
     // { key: "1", label: <Space><MyIcon type='icon-liangdu' />跟随系统</Space> },
-    { key: "2", label: <Space><MyIcon type='icon-brightj2' />亮色模式</Space> },
-    { key: "3", label: <Space><MyIcon type='icon-yejing' />暗色模式</Space> }
+    {
+      key: '2',
+      label: (
+        <Space>
+          <MyIcon type="icon-brightj2" />
+          亮色模式
+        </Space>
+      ),
+    },
+    {
+      key: '3',
+      label: (
+        <Space>
+          <MyIcon type="icon-yejing" />
+          暗色模式
+        </Space>
+      ),
+    },
   ];
   const onClick: MenuProps['onClick'] = ({ key }: any) => {
-    localStorage.setItem("navTheme", key)
-    setInitialState({ ...initialState, navTheme: key } as any)
-  }
+    localStorage.setItem('navTheme', key);
+    setInitialState({ ...initialState, navTheme: key } as any);
+  };
   return (
     <Space size={[0, 0]}>
       <Dropdown menu={{ items, onClick }}>
-        {
-          initialState?.navTheme === '2' ? <MyIcon type='icon-brightj2' /> : <MyIcon type='icon-yejing' />
-        }
+        {initialState?.navTheme === '2' ? (
+          <MyIcon type="icon-brightj2" />
+        ) : (
+          <MyIcon type="icon-yejing" />
+        )}
       </Dropdown>
-      {initialState?.menuType === 'miniApp' &&
+      {initialState?.menuType === 'miniApp' && (
         <DraggableButton ref={ref}>
-          <MenuChange menuType='distributor' data={null}>
-            <Button type='primary' shape='circle'><HomeOutlined /></Button>
+          <MenuChange menuType="distributor" data={null}>
+            <Button type="primary" shape="circle">
+              <HomeOutlined />
+            </Button>
           </MenuChange>
-        </DraggableButton >
-      }
-      <Tour open={open} onClose={() => {
-        setOpen(false)
-        localStorage.setItem("isOne", '1')
-      }} steps={[{
-        title: '返回主页',
-        description: <>   <Button type='primary' shape='circle'><HomeOutlined /></Button> 可随意拖动位置,点击可从当前页面返回到分销商平台主页!</>,
-        target: () => ref.current,
-      },]} />
+        </DraggableButton>
+      )}
+      <Tour
+        open={open}
+        onClose={() => {
+          setOpen(false);
+          localStorage.setItem('isOne', '1');
+        }}
+        steps={[
+          {
+            title: '返回主页',
+            description: (
+              <>
+                {' '}
+                <Button type="primary" shape="circle">
+                  <HomeOutlined />
+                </Button>{' '}
+                可随意拖动位置,点击可从当前页面返回到分销商平台主页!
+              </>
+            ),
+            target: () => ref.current,
+          },
+        ]}
+      />
     </Space>
   );
 };

+ 243 - 0
src/components/UploadFile/index.tsx

@@ -0,0 +1,243 @@
+import { useAjax } from '@/Hook/useAjax';
+import {
+  apiLongBookConfirmUploadLongBook,
+  apiLongBookUploadLongBook,
+} from '@/services/book/longbookList';
+import { apiShortBookUploadShortBook } from '@/services/book/shortbookList';
+import { InboxOutlined, MinusCircleOutlined } from '@ant-design/icons';
+import {
+  Button,
+  Card,
+  Col,
+  Form,
+  FormProps,
+  Input,
+  message,
+  Radio,
+  Row,
+  Space,
+  Typography,
+  UploadProps,
+} from 'antd';
+import { RcFile } from 'antd/es/upload';
+import Dragger from 'antd/es/upload/Dragger';
+import React, { useCallback, useMemo, useState } from 'react';
+const { Title } = Typography;
+
+function UploadFile(props: {
+  type: 'long' | 'short';
+  open: boolean;
+  bookId: string;
+  refresh?: () => void;
+}) {
+  let { type, bookId, refresh, open } = props;
+  let [file, setFile] = useState<RcFile | null>(null); //上传文件
+  let [longUpdata, setLongUpData] = useState<any>(null); //长篇小说上传后的结果
+  let [uploadType, setUploadType] = useState<any>(2); //新传或续传
+  const [form] = Form.useForm(); //新增修改小说form表单实例
+  let ShortBookUploadShortBook = useAjax((params) => apiShortBookUploadShortBook(params)); //短篇小说上传
+  let LongBookUploadLongBook = useAjax((params) => apiLongBookUploadLongBook(params)); //长篇小说上传
+  let LongBookConfirmUploadLongBook = useAjax((params) => apiLongBookConfirmUploadLongBook(params)); //长篇小说确认
+
+  let config: any = useMemo(() => {
+    let config = {};
+    if (!open) {
+      //关闭段落编辑时清空
+      setFile(null);
+      setLongUpData(null);
+    }
+    if (type === 'long') {
+      config = {
+        name: '长篇小说',
+        bookId,
+        api: LongBookUploadLongBook,
+        refresh,
+      };
+    } else {
+      config = {
+        name: '短篇小说',
+        bookId,
+        api: ShortBookUploadShortBook,
+        refresh,
+      };
+    }
+    return config;
+  }, [type, bookId, refresh, open]);
+  const uploadProprs: UploadProps = {
+    name: 'file',
+    multiple: false,
+    action: '#',
+    accept: '.txt',
+    fileList: file ? [file] : [],
+    // disabled: type === 'short' ? false : !!file,
+    beforeUpload: (file: RcFile) => {
+      submit(file);
+    },
+    customRequest: () => {},
+  };
+  const submit = useCallback(
+    (file: RcFile) => {
+      let formData = new FormData();
+      let data: any = { file, bookId: config.bookId };
+      // 短篇小说加入uploadType
+      if (config.name === '短篇小说') {
+        data.uploadType = uploadType;
+      }
+      Object.keys(data).forEach((key: string) => {
+        formData.append(key, data[key]);
+      });
+      config.api.run(formData).then((res: any) => {
+        if (res.code === 200) {
+          message.success('上传成功!');
+          // 短篇小说刷新页面
+          if (config.name === '短篇小说') {
+            config.refresh();
+          } else {
+            // 长篇小说存放数据
+            setLongUpData(res.data);
+            form.setFieldsValue({ chapters: res.data.chapters });
+          }
+          setFile(file);
+        }
+      });
+      console.log(config, file);
+    },
+    [config, uploadType, form],
+  );
+  // 表单错误
+  const onFinishFailed: FormProps['onFinishFailed'] = (errorInfo) => {
+    console.log('Failed:', errorInfo);
+  };
+  // 删除
+  const handleRemove = (name: any) => {
+    const chapters: any = form.getFieldValue('chapters');
+    const newChapters = chapters.map((chapter: any, index: any) => {
+      if (index === name) {
+        return { ...chapter, status: 1 }; // 设置status为1
+      }
+      return chapter;
+    });
+    console.log(chapters, newChapters);
+    form.setFieldsValue({ chapters: newChapters });
+  };
+  const longSubmit: FormProps['onFinish'] = (values) => {
+    console.log(values);
+    LongBookConfirmUploadLongBook.run({
+      ...values,
+      uploadType,
+      bookId,
+      fileUrl: longUpdata.fileUrl,
+    }).then((res) => {
+      if (res.code === 200) {
+        refresh?.();
+        setFile(null);
+        setLongUpData(null);
+      }
+    });
+  };
+  return (
+    <div>
+      <Card>
+        <Title style={{ marginTop: 0, textAlign: 'center' }}>{config?.name}上传</Title>
+        <Space direction="vertical" style={{ width: '100%' }}>
+          <Radio.Group
+            onChange={(e) => {
+              setUploadType(e.target.value);
+            }}
+            value={uploadType}
+          >
+            <Radio value={1}>新传小说</Radio>
+            <Radio value={2}>续传小说</Radio>
+          </Radio.Group>
+          <Dragger {...uploadProprs}>
+            <p className="ant-upload-drag-icon">
+              <InboxOutlined />
+            </p>
+            <p className="ant-upload-text">{config?.name}上传</p>
+            <p className="ant-upload-hint">点击或拖动文件至此处</p>
+            <p className="ant-upload-hint" color="#000">
+              当前为<span style={{ color: 'red' }}>{uploadType == 2 ? '续传' : '新传'}</span>
+              模式,会<span style={{ color: 'red' }}>{uploadType == 2 ? '保留' : '清除'}</span>
+              已有小说内容
+            </p>
+          </Dragger>
+          {
+            //
+            type === 'long' && longUpdata && (
+              <Row justify={'center'} style={{ marginTop: 20 }}>
+                <Col>
+                  <Form
+                    name="basic"
+                    labelCol={{ span: 5 }}
+                    wrapperCol={{ span: 16 }}
+                    style={{ minWidth: 600 }}
+                    onFinish={longSubmit}
+                    onFinishFailed={onFinishFailed}
+                    autoComplete="off"
+                    form={form}
+                  >
+                    <Form.List name="chapters">
+                      {(fields, { add, remove }) => {
+                        return (
+                          <>
+                            {fields.map(({ key, name, ...restField }, index) => {
+                              let status = form.getFieldValue(['chapters', name, 'status']);
+                              console.log(status);
+                              return (
+                                <React.Fragment key={key}>
+                                  <Form.Item
+                                    label={`章节标题`}
+                                    style={{ position: 'relative' }}
+                                    hidden={status === 1}
+                                  >
+                                    <Form.Item
+                                      {...restField}
+                                      name={[name, 'chapterTitle']}
+                                      rules={[{ required: true, message: '请补充标题' }]}
+                                      wrapperCol={{ span: 22 }}
+                                      style={{ marginBottom: 0 }}
+                                    >
+                                      <Input placeholder="请输入标题" />
+                                    </Form.Item>
+                                    <MinusCircleOutlined
+                                      onClick={() => {
+                                        handleRemove(name);
+                                      }}
+                                      style={{
+                                        position: 'absolute',
+                                        top: '50%',
+                                        right: 0,
+                                        transform: 'translateY(-50%)',
+                                      }}
+                                    />
+                                  </Form.Item>
+                                </React.Fragment>
+                              );
+                            })}
+                          </>
+                        );
+                      }}
+                    </Form.List>
+                    <Form.Item wrapperCol={{ offset: 0, span: 24 }}>
+                      <div style={{ display: 'flex', justifyContent: 'center' }}>
+                        <Button
+                          type="primary"
+                          htmlType="submit"
+                          loading={LongBookConfirmUploadLongBook.loading}
+                        >
+                          确认并提交
+                        </Button>
+                      </div>
+                    </Form.Item>
+                  </Form>
+                </Col>
+              </Row>
+            )
+          }
+        </Space>
+      </Card>
+    </div>
+  );
+}
+
+export default UploadFile;

+ 1 - 1
src/components/index.ts

@@ -9,4 +9,4 @@ import Footer from './Footer';
 import { Question, SelectLang } from './RightContent';
 import { AvatarDropdown, AvatarName } from './RightContent/AvatarDropdown';
 
-export { Footer, Question, SelectLang, AvatarDropdown, AvatarName };
+export { AvatarDropdown, AvatarName, Footer, Question, SelectLang };

+ 257 - 0
src/components/readBook/index.tsx

@@ -0,0 +1,257 @@
+import { MyIcon } from '@/global';
+import { useToken } from '@ant-design/pro-components';
+import { Button, Col, Drawer, Empty, Image, Row, Space } from 'antd';
+import { useEffect, useRef, useState } from 'react';
+function ReadBook(props: { next: (listData: { id: number }) => Promise<any>; listData: any }) {
+  let { next, listData } = props;
+  const divRef = useRef<HTMLDivElement>(null);
+  let { token } = useToken();
+  const [isFetching, setIsFetching] = useState(true);
+  const [newData, setNewData] = useState<any>(null);
+  const [content, setContent] = useState<any>(null);
+  const containerRef = useRef(null);
+  const wrapperRef = useRef(null);
+  useEffect(() => {
+    const handleKeyDown = (event: KeyboardEvent) => {
+      if (divRef.current) {
+        if (event.key === 'ArrowDown') {
+          divRef.current.scrollBy(0, 50);
+        } else if (event.key === 'ArrowUp') {
+          divRef.current.scrollBy(0, -50);
+        }
+      }
+    };
+    window.addEventListener('keydown', handleKeyDown);
+    return () => {
+      window.removeEventListener('keydown', handleKeyDown);
+    };
+  }, []);
+
+  async function getBookContent(params: any) {
+    let content = await next(params);
+    setContent(content);
+  }
+  useEffect(() => {
+    if (divRef.current) {
+      divRef.current.scrollTop = 0;
+    }
+  }, [content, divRef]);
+  console.log(content, listData);
+  return (
+    listData && (
+      <div
+        style={{
+          height: '85vh',
+          overflow: 'hidden',
+          display: 'flex',
+          justifyContent: 'space-around',
+        }}
+      >
+        <div
+          style={{
+            width: '20%',
+            flexFlow: 'column',
+            alignItems: 'center',
+            display: 'flex',
+            borderRight: '1px solid #efefef',
+          }}
+        >
+          <div style={{ marginBottom: 20, marginTop: 20 }}>
+            {listData?.vipFree && (
+              <MyIcon
+                type="icon-vipmianfei"
+                style={{ fontSize: 70, position: 'absolute', zIndex: 1 }}
+              />
+            )}
+            <Image
+              src={listData?.picUrl}
+              style={{ width: 150 }}
+              onError={(e: any) => {
+                e.target.src = localStorage.getItem('nocover');
+              }}
+            />
+          </div>
+          <Row style={{ width: '80%' }} gutter={[10, 5]}>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>
+                <span>作者</span>:
+              </span>
+              {listData?.authorName}
+            </Col>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>
+                <span>类别</span>:
+              </span>
+              {listData?.categoryName}
+            </Col>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>
+                <span>来源</span>:
+              </span>
+              {({ UPLOAD: '管理员', CREATOR: '创作者' } as any)[listData.source]}
+            </Col>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>
+                <span>频道</span>:
+              </span>
+              {listData?.corpUserName == 0 ? '男频' : '女频'}
+            </Col>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>标签:</span>{' '}
+              {listData?.labels
+                ?.map((tags: { id: string; name: string }, index: number) => {
+                  return tags?.name;
+                })
+                .join(',')}
+            </Col>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>
+                <span>状态</span>:
+              </span>
+              {['连载中', '已完结'][listData.bookStatus]}
+            </Col>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>评分:</span>
+              {listData?.score || 0}
+            </Col>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>点击量:</span>
+              {listData?.visitCount || 0}
+            </Col>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>总字数:</span>
+              {listData?.wordCount || 0}
+            </Col>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>上架状态:</span>
+              {['上架中', '已下架'][listData.shelveStatus]}
+            </Col>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>描述:</span>
+              {listData?.bookDesc}
+            </Col>
+          </Row>
+        </div>
+
+        <div
+          style={{
+            overflowY: 'auto',
+            display: 'flex',
+            flexFlow: 'column',
+            width: '75%',
+            paddingTop: 20,
+            paddingBottom: 20,
+          }}
+          ref={divRef}
+        >
+          <div>
+            {listData.list?.map((ele: any, index: number) => (
+              <a
+                key={index}
+                style={{
+                  display: 'inline-block',
+                  marginRight: ele.index % 4 === 0 ? 0 : 20,
+                  marginBottom: 10,
+                  width: 190,
+                  overflow: 'hidden',
+                }}
+                onClick={() => {
+                  getBookContent({
+                    bookId: ele?.chapterInfo?.bookId,
+                    chapterNo: ele?.chapterInfo?.chapterNo,
+                  });
+                }}
+              >
+                <div style={{ display: 'flex', alignItems: 'center' }}>
+                  {ele.needPay && <MyIcon type="icon-jiesuo" style={{ marginRight: 5 }} />}
+                  <span>{ele?.chapterInfo?.chapterName}</span>
+                </div>
+              </a>
+            ))}
+            {listData.list?.length === 0 && (
+              <div style={{ width: '100%', height: '100%', marginTop: '10%' }}>
+                <Empty
+                  description={
+                    <Space direction="vertical">
+                      <div>暂无章节</div>
+                    </Space>
+                  }
+                />
+              </div>
+            )}
+          </div>
+          {/* 阅读小说 */}
+          <Drawer
+            open={!!content}
+            placement="right"
+            onClose={() => {
+              setContent(null);
+            }}
+            footer={null}
+            width={'60%'}
+            destroyOnClose={true}
+            styles={{ body: { padding: 0 } }}
+            closeIcon={false}
+            title={<div style={{ fontSize: 20 }}>{content?.chapterName}</div>}
+          >
+            {content && (
+              <>
+                <div
+                  ref={divRef}
+                  style={{
+                    borderTop: '1px solid #efefef',
+                    fontSize: 17,
+                    lineHeight: 2,
+                    padding: '0 40px',
+                    height: '87vh',
+                    overflowY: 'auto',
+                    paddingBottom: 50,
+                    paddingTop: 15,
+                    boxSizing: 'border-box',
+                  }}
+                  dangerouslySetInnerHTML={{ __html: content.content.replace(/\n/g, '<br/>') }}
+                ></div>
+                <div
+                  style={{
+                    borderTop: '1px solid #efefef',
+                    display: 'flex',
+                    justifyContent: 'center',
+                  }}
+                >
+                  <Space style={{ marginTop: 15 }}>
+                    {content?.chapterNo > 1 && (
+                      <Button
+                        onClick={() => {
+                          getBookContent({
+                            bookId: content?.bookId,
+                            chapterNo: content?.chapterNo - 1,
+                          });
+                        }}
+                      >
+                        上一章
+                      </Button>
+                    )}
+                    {content?.chapterNo < listData?.list?.length && (
+                      <Button
+                        onClick={() => {
+                          getBookContent({
+                            bookId: content?.bookId,
+                            chapterNo: content?.chapterNo + 1,
+                          });
+                        }}
+                      >
+                        下一章
+                      </Button>
+                    )}
+                  </Space>
+                </div>
+              </>
+            )}
+          </Drawer>
+        </div>
+      </div>
+    )
+  );
+}
+
+export default ReadBook;

+ 189 - 0
src/components/readText/index.tsx

@@ -0,0 +1,189 @@
+import { MyIcon } from '@/global';
+import { useToken } from '@ant-design/pro-components';
+import { Col, Image, Row, Spin } from 'antd';
+import { useEffect, useRef, useState } from 'react';
+function ReadText(props: {
+  next: (data: { pageNum: number; pageSize: number; id: number }) => Promise<any>;
+  data: any;
+}) {
+  let { next, data } = props;
+  const divRef = useRef<HTMLDivElement>(null);
+  const [isFetching, setIsFetching] = useState(true);
+  const [newData, setNewData] = useState<any>(null);
+  let { token } = useToken();
+  // 存放首次的size
+  useEffect(() => {
+    setNewData(data);
+  }, []);
+
+  useEffect(() => {
+    const handleKeyDown = (event: KeyboardEvent) => {
+      if (divRef.current) {
+        if (event.key === 'ArrowDown') {
+          divRef.current.scrollBy(0, 50);
+        } else if (event.key === 'ArrowUp') {
+          divRef.current.scrollBy(0, -50);
+        }
+      }
+    };
+
+    window.addEventListener('keydown', handleKeyDown);
+
+    return () => {
+      window.removeEventListener('keydown', handleKeyDown);
+    };
+  }, []);
+
+  const handleScroll = () => {
+    if (divRef.current) {
+      const { scrollTop, scrollHeight, clientHeight } = divRef.current;
+      if (scrollTop + clientHeight >= scrollHeight / 2 && isFetching) {
+        if (newData.current * newData.size < newData.total) {
+          getText();
+        }
+      }
+      if (scrollTop + clientHeight >= scrollHeight) {
+        console.log('到底了');
+        getText();
+      }
+    }
+  };
+
+  async function getText() {
+    if (newData.current * newData.size < newData.total) {
+      setIsFetching(false);
+      console.log('还有内容');
+      let r = await next({ ...data, pageNum: 1, pageSize: newData.size + data.size }); // Replace with actual data
+      setNewData(r);
+      setIsFetching(true);
+    }
+  }
+  return (
+    data && (
+      <div
+        style={{
+          height: '90vh',
+          overflow: 'hidden',
+          display: 'flex',
+          justifyContent: 'space-around',
+        }}
+      >
+        <div
+          style={{
+            width: '20%',
+            flexFlow: 'column',
+            alignItems: 'center',
+            display: 'flex',
+            borderRight: '1px solid #efefef',
+          }}
+        >
+          <div style={{ marginBottom: 20, marginTop: 20 }}>
+            {data?.vipFree && (
+              <MyIcon
+                type="icon-vipmianfei"
+                style={{ fontSize: 70, position: 'absolute', zIndex: 1 }}
+              />
+            )}
+            <Image
+              src={data?.picUrl}
+              style={{ width: 150 }}
+              onError={(e: any) => {
+                e.target.src = localStorage.getItem('nocover');
+              }}
+            />
+          </div>
+          <Row style={{ width: '80%' }} gutter={[10, 5]}>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>
+                <span>作者</span>:
+              </span>
+              {data?.authorName}
+            </Col>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>
+                <span>类别</span>:
+              </span>
+              {data?.categoryName}
+            </Col>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>
+                <span>来源</span>:
+              </span>
+              {({ UPLOAD: '管理员', CREATOR: '创作者' } as any)[data.source]}
+            </Col>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>
+                <span>频道</span>:
+              </span>
+              {data?.corpUserName == 0 ? '男频' : '女频'}
+            </Col>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>标签:</span>{' '}
+              {data?.labels
+                ?.map((tags: { id: string; name: string }, index: number) => {
+                  return tags?.name;
+                })
+                .join(',')}
+            </Col>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>
+                <span>状态</span>:
+              </span>
+              {['连载中', '已完结'][data.bookStatus]}
+            </Col>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>评分:</span>
+              {data?.score || 0}
+            </Col>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>点击量:</span>
+              {data?.visitCount || 0}
+            </Col>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>总字数:</span>
+              {data?.wordCount || 0}
+            </Col>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>上架状态:</span>
+              {['上架中', '已下架'][data.shelveStatus]}
+            </Col>
+            <Col span={24} style={{ fontSize: 14, color: token.colorTextSecondary }}>
+              <span style={{ color: token.colorText }}>描述:</span>
+              {data?.bookDesc}
+            </Col>
+          </Row>
+        </div>
+        <div
+          style={{
+            overflowY: 'auto',
+            display: 'flex',
+            flexFlow: 'column',
+            width: '75%',
+            paddingBottom: 50,
+            boxSizing: 'border-box',
+          }}
+          onScroll={handleScroll}
+          ref={divRef}
+        >
+          <Spin spinning={!data?.size}>
+            {newData?.records?.map(
+              (item: { shortBookContentVO: { contentId: number; content: string } }) => {
+                return (
+                  <div
+                    style={{ fontSize: 17, lineHeight: 2 }}
+                    key={item.shortBookContentVO.contentId}
+                    dangerouslySetInnerHTML={{
+                      __html: item.shortBookContentVO.content.replace(/\n/g, '<br/>'),
+                    }}
+                  ></div>
+                );
+              },
+            )}
+          </Spin>
+        </div>
+      </div>
+    )
+  );
+}
+
+export default ReadText;

+ 3 - 0
src/components/uploadImg/index.less

@@ -0,0 +1,3 @@
+.upLoadTrue {
+  margin: 20px 0;
+}

+ 124 - 0
src/components/uploadImg/index.tsx

@@ -0,0 +1,124 @@
+import CropperImg from '@/components/CropperImg';
+import { useAjax } from '@/Hook/useAjax';
+import { useOss } from '@/Hook/useOss';
+import { MediaTypeProps, upLoadMediaApi } from '@/services/global';
+import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
+import { message, Upload } from 'antd';
+import { RcFile } from 'antd/es/upload';
+import dayjs from 'dayjs';
+import React, { useEffect, useState } from 'react';
+import styles from './index.less';
+type CompareType = '<' | '<=' | '>' | '>=' | '=' | '!=';
+
+interface Props {
+  type?: MediaTypeProps;
+  value?: string;
+  onChange?: (data: { value: string; file: RcFile; name: string }) => void;
+  size?: { width: number; height: number; evalW: CompareType; evalH: CompareType; msg: string };
+  // 是否裁剪
+  isCropper?: boolean;
+  isEdit?: boolean;
+}
+
+const UploadImg: React.FC<Props> = ({ value, onChange, size, type, isCropper, isEdit }) => {
+  /********************************/
+  const [visible, setVisible] = useState<boolean>(false);
+  const [file, setFile] = useState<RcFile>();
+  const upLoadMedia = useAjax((params) => upLoadMediaApi(params));
+  const ossUpload = useOss(true);
+  const [imageUrl, setImageUrl] = useState<string>('');
+  /********************************/
+
+  useEffect(() => {
+    setImageUrl(value || '');
+  }, [value]);
+
+  const uploadButton = (
+    <div>
+      <div style={{ height: 45, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
+        {' '}
+        {upLoadMedia.loading ? <LoadingOutlined /> : <PlusOutlined />}
+      </div>
+      <div style={{ display: 'flex', flexFlow: 'column' }}>
+        <span>上传图片</span>
+        <span style={{ color: '#999', fontSize: 10 }}>
+          {size?.width} * {size?.height}
+        </span>
+      </div>
+    </div>
+  );
+
+  const upload = (file: RcFile) => {
+    let name = dayjs().valueOf().toString();
+    ossUpload.run(file, name).then((res: any) => {
+      if (res?.data) {
+        let [fileName, nameSuffix] = file.name.split('.');
+        onChange?.(res.data.url);
+      }
+    });
+  };
+
+  return (
+    <>
+      <Upload
+        name="avatar"
+        listType="picture-card"
+        accept="image/png,image/jpg,image/jpeg"
+        action="#"
+        showUploadList={false}
+        customRequest={() => {}}
+        className={imageUrl ? styles.upLoadTrue : ''}
+        beforeUpload={(file: RcFile): any => {
+          if (file.size > 10485760) {
+            message.error('图片大小大于10M,请压缩在上传');
+            return;
+          }
+          let img: any = new Image();
+          let _URL = window.URL || window.webkitURL;
+          img.onload = function (e: any) {
+            if (
+              (size?.width && eval(`${this.width} ${size.evalW} ${size?.width}`)) ||
+              (size?.height && eval(`${this.height} ${size?.evalH} ${size?.height}`))
+            ) {
+              if (
+                isCropper &&
+                ((isEdit &&
+                  eval(`${this.width} >= ${size?.width}`) &&
+                  eval(`${this.height} >= ${size?.height}`)) ||
+                  !isEdit)
+              ) {
+                setFile(file);
+                setVisible(true);
+              } else {
+                message.error(
+                  `传入的图片大小不符, 上传图片宽度${this.width}高度${this.height}, ${size?.msg}`,
+                );
+              }
+              return;
+            }
+            upload(file);
+          };
+          img.src = _URL.createObjectURL(file);
+        }}
+      >
+        {imageUrl ? <img src={imageUrl} alt="avatar" style={{ width: '100%' }} /> : uploadButton}
+      </Upload>
+      {/* 裁剪 */}
+      {visible && (
+        <CropperImg
+          visible={visible}
+          size={size}
+          isEdit={isEdit}
+          onClose={() => setVisible(false)}
+          file={file}
+          onChange={(fileList: any[], file: any) => {
+            upload(file);
+            setVisible(false);
+          }}
+        />
+      )}
+    </>
+  );
+};
+
+export default React.memo(UploadImg);

+ 1 - 1
src/global.less

@@ -34,7 +34,7 @@ ul,
 ol {
   list-style: none;
 }
-a{
+a {
   text-decoration: none;
 }
 @media (max-width: 768px) {

+ 5 - 5
src/global.tsx

@@ -1,16 +1,16 @@
+import { createFromIconfontCN } from '@ant-design/icons';
 import { useIntl } from '@umijs/max';
 import { Button, message, notification } from 'antd';
 import defaultSettings from '../config/defaultSettings';
-import { createFromIconfontCN } from '@ant-design/icons';
-import nocover from '../public/nocover.jpg'
+import nocover from '../public/nocover.jpg';
 const { pwa } = defaultSettings;
 const isHttps = document.location.protocol === 'https:';
-export let scriptUrl = "//at.alicdn.com/t/c/font_4644725_v9fyft7xju9.js"//线上icon
+export let scriptUrl = '//at.alicdn.com/t/c/font_4644725_b4358eiszy.js'; //线上icon
 // 自定义icon组件用于线上icon
 export const MyIcon = createFromIconfontCN({
-  scriptUrl// 在 iconfont.cn 上生成
+  scriptUrl, // 在 iconfont.cn 上生成
 });
-localStorage.setItem("nocover",nocover)
+localStorage.setItem('nocover', nocover);
 const clearCache = () => {
   // remove all caches
   if (window.caches) {

+ 2 - 2
src/locales/zh-CN/menu.ts

@@ -50,6 +50,6 @@ export default {
   'menu.editor.mind': '脑图编辑器',
   'menu.editor.koni': '拓扑编辑器',
   // 自定义
-  'menu.book':"小说中心",
-  'menu.book.bookManage':"小说管理"
+  'menu.book': '小说中心',
+  'menu.book.bookManage': '小说管理',
 };

+ 107 - 30
src/models/global.tsx

@@ -1,36 +1,113 @@
-import { useReducer, useState } from "react"
+import { apibookAuthorListAll } from '@/services/book/author';
+import { apiBookContentLabelListAll } from '@/services/book/bookTags';
+import { apibookCategoryListAll } from '@/services/book/classifyList';
+import { enumDictList } from '@/services/global';
+import { useReducer } from 'react';
 
 type State = {
-    enumList?: { [key: string]: any[] },//枚举
-    labelList?: any[],//标签列表
-    categoryList?: any[],//分类列表
-    authList?: any[],//作者列表
-}
+  enumList?: { [key: string]: any[] }; //枚举
+  labelList?: any[]; //标签列表
+  categoryList?: any[]; //分类列表
+  authList?: any[]; //作者列表
+};
 type Action = {
-    params?: any,
-    type: 'setEnum' | 'setLabelList' | 'setCategoryList' | 'setAuthList' | "setAll",
-}
+  params?: any;
+  type: 'setEnum' | 'setLabelList' | 'setCategoryList' | 'setAuthList' | 'setAll';
+};
 export function reducer(state: State, action: Action) {
-    let { type, params } = action
-    switch (type) {
-        case 'setEnum':
-            return { ...state, enumList: params?.enumList }
-        case 'setLabelList':
-            return { ...state, labelList: params?.labelList }
-        case 'setCategoryList':
-            return { ...state, categoryList: params?.categoryList }
-        case 'setAuthList':
-            return { ...state, authList: params?.authList }
-        case 'setAll':
-            return { ...state, ...params }
-        default:
-            return state;
-    }
+  let { type, params } = action;
+  switch (type) {
+    case 'setEnum':
+      return { ...state, enumList: params?.enumList };
+    case 'setLabelList':
+      return { ...state, labelList: params?.labelList };
+    case 'setCategoryList':
+      return { ...state, categoryList: params?.categoryList };
+    case 'setAuthList':
+      return { ...state, authList: params?.authList };
+    case 'setAll':
+      return { ...state, ...params };
+    default:
+      return state;
+  }
 }
-export default (): { state: State } => {
-    const [state, dispatch] = useReducer(reducer, {})
-    let isLoding = false
-    return {
-        state
+export default (): {
+  state: State;
+  init: () => void;
+  getLabelAndClassList: (params?: { workDirection: number | string }) => Promise<boolean>;
+} => {
+  const [state, dispatch] = useReducer(reducer, {});
+  let isLoding = false;
+  const init = async () => {
+    if (isLoding) {
+      return;
+    }
+    console.log('初始化');
+    isLoding = true;
+    let enumData: any = await enumDictList()
+      .then((res) => res.data)
+      .catch((err) => {
+        console.log(err);
+      });
+    let authList = await apibookAuthorListAll()
+      .then((res) => res.data)
+      .catch((err) => {
+        console.log(err);
+      });
+    let enumList: { [key: string]: any } = {};
+    if (enumData) {
+      Object.values(enumData.enums).map((item: any) => {
+        enumList[item.name] = item;
+      });
+    }
+    // 收费货币
+    if (enumList && !enumList.PAYMENT_CATEGORY) {
+      enumList.PAYMENT_CATEGORY = {};
+      enumList.PAYMENT_CATEGORY.values = [
+        { value: 0, description: '现金' },
+        { value: 1, description: '书币' },
+      ];
+    }
+    //是否开放给所有分销商
+    if (enumList && !enumList?.PUBLIC_DISTRIBUTOR) {
+      enumList.PUBLIC_DISTRIBUTOR = {};
+      enumList.PUBLIC_DISTRIBUTOR.values = [
+        { value: false, description: '否' },
+        { value: true, description: '是' },
+      ];
+    }
+    // 是否允许分销商配置价格
+    if (enumList && !enumList?.ALLOW_DISTRIBUTOR_CONFIG) {
+      enumList.ALLOW_DISTRIBUTOR_CONFIG = {};
+      enumList.ALLOW_DISTRIBUTOR_CONFIG.values = [
+        { value: false, description: '否' },
+        { value: true, description: '是' },
+      ];
+    }
+    dispatch({ type: 'setAll', params: { enumList, authList } });
+    isLoding = false;
+  };
+  const getLabelAndClassList = async (params?: { workDirection: number | string }) => {
+    let labelList = null;
+    let categoryList = null;
+    if (typeof params?.workDirection === 'number') {
+      labelList = await apiBookContentLabelListAll(params)
+        .then((res) => res.data)
+        .catch((err) => {
+          console.log(err);
+        });
+      categoryList = await apibookCategoryListAll(params)
+        .then((res) => res.data)
+        .catch((err) => {
+          console.log(err);
+        });
     }
-}
+    dispatch({ type: 'setAll', params: { ...state, labelList, categoryList } });
+    return true;
+  };
+  return {
+    state,
+    init,
+    getLabelAndClassList,
+  };
+};

+ 308 - 284
src/pages/User/Login/index.tsx

@@ -1,24 +1,15 @@
-
-import {
-  LockOutlined,
-  MobileOutlined,
-  SwapRightOutlined,
-} from '@ant-design/icons';
-import {
-  LoginForm,
-  ProFormCaptcha,
-  ProFormText,
-} from '@ant-design/pro-components';
-import { FormattedMessage, history, useIntl, useModel, Helmet } from '@umijs/max';
+import { useAjax } from '@/Hook/useAjax';
+import { api } from '@/services/api';
+import { getCode, getNoteCode, phoneLogin } from '@/services/login';
+import { LockOutlined, MobileOutlined, SwapRightOutlined } from '@ant-design/icons';
+import { LoginForm, ProFormCaptcha, ProFormText } from '@ant-design/pro-components';
+import { FormattedMessage, Helmet, history, useIntl, useModel } from '@umijs/max';
 import { Alert, Button, message, Tabs } from 'antd';
-import Settings from '../../../../config/defaultSettings';
+import { createStyles } from 'antd-style';
 import React, { useEffect, useState } from 'react';
 import { flushSync } from 'react-dom';
-import { createStyles } from 'antd-style';
-import { useAjax } from '@/Hook/useAjax';
-import { getCode, getNoteCode, phoneLogin } from '@/services/login';
-import { api } from '@/services/api';
-import style from './style.less'
+import Settings from '../../../../config/defaultSettings';
+import style from './style.less';
 const useStyles = createStyles(({ token }) => {
   return {
     action: {
@@ -52,12 +43,9 @@ const useStyles = createStyles(({ token }) => {
         "url('https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/V-_oS6r-i7wAAAAAAAAAAAAAFl94AQBr')",
       backgroundSize: '100% 100%',
     },
-
   };
 });
 
-
-
 const LoginMessage: React.FC<{
   content: string;
 }> = ({ content }) => {
@@ -79,15 +67,15 @@ const Login: React.FC = () => {
   const { initialState, setInitialState } = useModel('@@initialState');
   const { styles } = useStyles();
   const intl = useIntl();
-  const [companyList, setCompanyList] = useState<any[]>([])
+  const [companyList, setCompanyList] = useState<any[]>([]);
 
-  const loginByPCodeFn = useAjax((data) => phoneLogin(data))//手机登录
+  const loginByPCodeFn = useAjax((data) => phoneLogin(data)); //手机登录
   //判断是否已登录强制
-  useEffect(()=>{
-    if(localStorage.getItem("Admin-Token")){
-      history.push("/")
+  useEffect(() => {
+    if (localStorage.getItem('Admin-Token')) {
+      history.push('/');
     }
-  },[])
+  }, []);
 
   const fetchUserInfo = async (data: any) => {
     // const userInfo = await initialState?.fetchUserInfo?.();
@@ -106,25 +94,27 @@ const Login: React.FC = () => {
   const handleSubmit = async (values: any) => {
     try {
       // 登录
-      console.log(type, values)
+      console.log(type, values);
       const urlParams = new URL(window.location.href).searchParams;
       let api = loginByPCodeFn;
-      let newValues = { phone: values.mobile, code: values.captcha }
-      api.run(newValues).then(async res => {
+      let newValues = { phone: values.mobile, code: values.captcha };
+      api.run(newValues).then(async (res) => {
         if (res.data) {
           try {
             if (res.code === 200) {
-              localStorage.setItem('Admin-Token', res?.data?.token)
-              let companyInfo = res?.data?.companyRelationInfo?.filter((item: { companyId: number }) => item.companyId !== 4 && item.companyId !== 3)
-              await fetchUserInfo(res.data.userInfo)
+              localStorage.setItem('Admin-Token', res?.data?.token);
+              let companyInfo = res?.data?.companyRelationInfo?.filter(
+                (item: { companyId: number }) => item.companyId !== 4 && item.companyId !== 3,
+              );
+              await fetchUserInfo(res.data.userInfo);
               if (companyInfo?.length === 0) {
-                localStorage.removeItem('Admin-Token')
-                message.error('登录失败,请用趣程运营平台账号登录')
-                return
+                localStorage.removeItem('Admin-Token');
+                message.error('登录失败,请用趣程运营平台账号登录');
+                return;
               } else if (companyInfo?.length === 1) {
-                setCompanyHandle(res?.data?.companyRelationInfo[0].companyId)
+                setCompanyHandle(res?.data?.companyRelationInfo[0].companyId);
               } else {
-                setCompanyList(companyInfo)
+                setCompanyList(companyInfo);
               }
               return;
             }
@@ -133,8 +123,8 @@ const Login: React.FC = () => {
             message.error('登录失败,请重试!');
           }
         }
-      })
-      return
+      });
+      return;
     } catch (error) {
       const defaultLoginFailureMessage = intl.formatMessage({
         id: 'pages.login.failure',
@@ -149,261 +139,295 @@ const Login: React.FC = () => {
   const setCompanyHandle = (companyId: number) => {
     fetch(api + `/erp/user/chooseCompany/${companyId}`, {
       method: 'PUT',
-      headers: { ['Authorization']: 'Bearer ' + localStorage.getItem('Admin-Token') }
-    }).then(res => res.json()).then((res: any) => {
-      if (res?.code === 200) {
-        // 验证服务器是否升级
-        localStorage.setItem('Admin-Token', res?.data?.token)
-        window.location.href = '/';
-      }
-    }).catch()
-  }
+      headers: { ['Authorization']: 'Bearer ' + localStorage.getItem('Admin-Token') },
+    })
+      .then((res) => res.json())
+      .then((res: any) => {
+        if (res?.code === 200) {
+          // 验证服务器是否升级
+          localStorage.setItem('Admin-Token', res?.data?.token);
+          window.location.href = '/';
+        }
+      })
+      .catch();
+  };
   const { status, type: loginType } = userLoginState;
-  return <>
-    {/* */}
-    {<div className={styles.container}>
-      <Helmet>
-        <title>
-          {intl.formatMessage({
-            id: 'menu.login',
-            defaultMessage: '登录页',
-          })}
-          - {Settings.title}
-        </title>
-      </Helmet>
-      <div
-        style={{
-          flex: '1',
-          padding: '32px 0',
-          display: 'flex',
-          alignItems: 'center',
-          justifyContent: 'center'
-        }}
-      >
-        <div style={{ height: 'auto', overflow: 'hidden', marginTop: '-10%' }}>
-          {
-            localStorage.getItem('Admin-Token') && companyList?.length > 0 ? <div className={`${style.company}`}>
-              <div className={style.companyAccount}>
-                <h3 className={style.title}>请选择公司账户登录</h3>
-                <div className={style.chooseTableBlock}>
-                  {
-                    companyList?.map((item: any) => <div className={style.acTableLine} key={item?.companyId} onClick={() => { setCompanyHandle(item.companyId) }}>
-                      <div className={style.actname}>{item?.companyInfo?.companyName}</div>
-                      <div className={style.right}> <div className={style.actcha}>{item?.powerLevel === 999 ? '超级管理员' : item?.powerLevel === 100 ? '系统管理员' : item?.powerLevel === 99 ? '管理员' : '普通用户'}</div> <SwapRightOutlined className={style.iconRight} /></div>
-                    </div>)
-                  }
-                </div>
-                <div className={style.button}>
-                  <Button type="link" onClick={() => {
-                    localStorage.removeItem('Admin-Token')
-                    location.reload()
-                  }}>更换账号</Button>
+  return (
+    <>
+      {/* */}
+      {
+        <div className={styles.container}>
+          <Helmet>
+            <title>
+              {intl.formatMessage({
+                id: 'menu.login',
+                defaultMessage: '登录页',
+              })}
+              - {Settings.title}
+            </title>
+          </Helmet>
+          <div
+            style={{
+              flex: '1',
+              padding: '32px 0',
+              display: 'flex',
+              alignItems: 'center',
+              justifyContent: 'center',
+            }}
+          >
+            <div style={{ height: 'auto', overflow: 'hidden', marginTop: '-10%' }}>
+              {localStorage.getItem('Admin-Token') && companyList?.length > 0 ? (
+                <div className={`${style.company}`}>
+                  <div className={style.companyAccount}>
+                    <h3 className={style.title}>请选择公司账户登录</h3>
+                    <div className={style.chooseTableBlock}>
+                      {companyList?.map((item: any) => (
+                        <div
+                          className={style.acTableLine}
+                          key={item?.companyId}
+                          onClick={() => {
+                            setCompanyHandle(item.companyId);
+                          }}
+                        >
+                          <div className={style.actname}>{item?.companyInfo?.companyName}</div>
+                          <div className={style.right}>
+                            {' '}
+                            <div className={style.actcha}>
+                              {item?.powerLevel === 999
+                                ? '超级管理员'
+                                : item?.powerLevel === 100
+                                ? '系统管理员'
+                                : item?.powerLevel === 99
+                                ? '管理员'
+                                : '普通用户'}
+                            </div>{' '}
+                            <SwapRightOutlined className={style.iconRight} />
+                          </div>
+                        </div>
+                      ))}
+                    </div>
+                    <div className={style.button}>
+                      <Button
+                        type="link"
+                        onClick={() => {
+                          localStorage.removeItem('Admin-Token');
+                          location.reload();
+                        }}
+                      >
+                        更换账号
+                      </Button>
+                    </div>
+                  </div>
                 </div>
-              </div>
-
-            </div> : <LoginForm
-              contentStyle={{
-                minWidth: 280,
-                maxWidth: '75vw',
-              }}
-              title="小说管理后台"
-              loading={loginByPCodeFn?.loading || loginByPCodeFn?.loading}
-              onFinish={async (values) => {
-                await handleSubmit(values as API.LoginParams);
-              }}
-            >
-              <Tabs
-                activeKey={type}
-                onChange={setType}
-                centered
-                items={[
-                  {
-                    key: 'account',
-                    label: intl.formatMessage({
-                      id: 'pages.login.accountLogin.tab',
-                      defaultMessage: '钉钉登录',
-                    }),
-                  },
-                  {
-                    key: 'mobile',
-                    label: intl.formatMessage({
-                      id: 'pages.login.phoneLogin.tab',
-                      defaultMessage: '手机号登录',
-                    }),
-                  },
-                ]}
-              />
-
-              {status === 'error' && loginType === 'account' && <LoginMessage content="验证码错误" />}
-              {type === 'account' && (
-                <>
-                  <ProFormText
-                    fieldProps={{
-                      size: 'large',
-                      prefix: <MobileOutlined />,
-                    }}
-                    name="mobile"
-                    placeholder={intl.formatMessage({
-                      id: 'pages.login.phoneNumber.placeholder',
-                      defaultMessage: '手机号',
-                    })}
-                    rules={[
-                      {
-                        required: true,
-                        message: (
-                          <FormattedMessage
-                            id="pages.login.phoneNumber.required"
-                            defaultMessage="请输入手机号!"
-                          />
-                        ),
-                      },
+              ) : (
+                <LoginForm
+                  contentStyle={{
+                    minWidth: 280,
+                    maxWidth: '75vw',
+                  }}
+                  title="小说管理后台"
+                  loading={loginByPCodeFn?.loading || loginByPCodeFn?.loading}
+                  onFinish={async (values) => {
+                    await handleSubmit(values as API.LoginParams);
+                  }}
+                >
+                  <Tabs
+                    activeKey={type}
+                    onChange={setType}
+                    centered
+                    items={[
                       {
-                        pattern: /^1\d{10}$/,
-                        message: (
-                          <FormattedMessage
-                            id="pages.login.phoneNumber.invalid"
-                            defaultMessage="手机号格式错误!"
-                          />
-                        ),
+                        key: 'account',
+                        label: intl.formatMessage({
+                          id: 'pages.login.accountLogin.tab',
+                          defaultMessage: '钉钉登录',
+                        }),
                       },
-                    ]}
-                  />
-                  <ProFormCaptcha
-                    fieldProps={{
-                      size: 'large',
-                      prefix: <LockOutlined />,
-                    }}
-                    phoneName='mobile'
-                    captchaProps={{
-                      size: 'large',
-                    }}
-                    placeholder={intl.formatMessage({
-                      id: 'pages.login.captcha.placeholder',
-                      defaultMessage: '请输入验证码',
-                    })}
-                    captchaTextRender={(timing, count) => {
-                      if (timing) {
-                        return `${count} ${intl.formatMessage({
-                          id: 'pages.getCaptchaSecondText',
-                          defaultMessage: '获取验证码',
-                        })}`;
-                      }
-                      return intl.formatMessage({
-                        id: 'pages.login.phoneLogin.getVerificationCode',
-                        defaultMessage: '获取验证码',
-                      });
-                    }}
-                    name="captcha"
-                    rules={[
                       {
-                        required: true,
-                        message: (
-                          <FormattedMessage
-                            id="pages.login.captcha.required"
-                            defaultMessage="请输入验证码!"
-                          />
-                        ),
+                        key: 'mobile',
+                        label: intl.formatMessage({
+                          id: 'pages.login.phoneLogin.tab',
+                          defaultMessage: '手机号登录',
+                        }),
                       },
                     ]}
-                    onGetCaptcha={async (mobile) => {
-                      const result = await getCode(mobile);
-                      if (!result) {
-                        return;
-                      }
-                      message.success('获取验证码成功!请查看钉钉消息!');
-                    }}
                   />
-                </>
-              )}
 
-              {status === 'error' && loginType === 'mobile' && <LoginMessage content="验证码错误" />}
-              {type === 'mobile' && (
-                <>
-                  <ProFormText
-                    fieldProps={{
-                      size: 'large',
-                      prefix: <MobileOutlined />,
-                    }}
-                    name="mobile"
-                    placeholder={intl.formatMessage({
-                      id: 'pages.login.phoneNumber.placeholder',
-                      defaultMessage: '手机号',
-                    })}
-                    rules={[
-                      {
-                        required: true,
-                        message: (
-                          <FormattedMessage
-                            id="pages.login.phoneNumber.required"
-                            defaultMessage="请输入手机号!"
-                          />
-                        ),
-                      },
-                      {
-                        pattern: /^1\d{10}$/,
-                        message: (
-                          <FormattedMessage
-                            id="pages.login.phoneNumber.invalid"
-                            defaultMessage="手机号格式错误!"
-                          />
-                        ),
-                      },
-                    ]}
-                  />
-                  <ProFormCaptcha
-                    fieldProps={{
-                      size: 'large',
-                      prefix: <LockOutlined />,
-                    }}
-                    phoneName='mobile'
-                    captchaProps={{
-                      size: 'large',
-                    }}
-                    placeholder={intl.formatMessage({
-                      id: 'pages.login.captcha.placeholder',
-                      defaultMessage: '请输入验证码',
-                    })}
-                    captchaTextRender={(timing, count) => {
-                      if (timing) {
-                        return `${count} ${intl.formatMessage({
-                          id: 'pages.getCaptchaSecondText',
-                          defaultMessage: '获取验证码',
-                        })}`;
-                      }
-                      return intl.formatMessage({
-                        id: 'pages.login.phoneLogin.getVerificationCode',
-                        defaultMessage: '获取验证码',
-                      });
-                    }}
-                    name="captcha"
-                    rules={[
-                      {
-                        required: true,
-                        message: (
-                          <FormattedMessage
-                            id="pages.login.captcha.required"
-                            defaultMessage="请输入验证码!"
-                          />
-                        ),
-                      },
-                    ]}
-                    onGetCaptcha={async (mobile) => {
-                      const result = await getNoteCode(mobile);
-                      if (!result) {
-                        return;
-                      }
-                      message.success('获取验证码成功!请查看手机短信!');
-                    }}
-                  />
-                </>
-              )}
-            </LoginForm>
-          }
+                  {status === 'error' && loginType === 'account' && (
+                    <LoginMessage content="验证码错误" />
+                  )}
+                  {type === 'account' && (
+                    <>
+                      <ProFormText
+                        fieldProps={{
+                          size: 'large',
+                          prefix: <MobileOutlined />,
+                        }}
+                        name="mobile"
+                        placeholder={intl.formatMessage({
+                          id: 'pages.login.phoneNumber.placeholder',
+                          defaultMessage: '手机号',
+                        })}
+                        rules={[
+                          {
+                            required: true,
+                            message: (
+                              <FormattedMessage
+                                id="pages.login.phoneNumber.required"
+                                defaultMessage="请输入手机号!"
+                              />
+                            ),
+                          },
+                          {
+                            pattern: /^1\d{10}$/,
+                            message: (
+                              <FormattedMessage
+                                id="pages.login.phoneNumber.invalid"
+                                defaultMessage="手机号格式错误!"
+                              />
+                            ),
+                          },
+                        ]}
+                      />
+                      <ProFormCaptcha
+                        fieldProps={{
+                          size: 'large',
+                          prefix: <LockOutlined />,
+                        }}
+                        phoneName="mobile"
+                        captchaProps={{
+                          size: 'large',
+                        }}
+                        placeholder={intl.formatMessage({
+                          id: 'pages.login.captcha.placeholder',
+                          defaultMessage: '请输入验证码',
+                        })}
+                        captchaTextRender={(timing, count) => {
+                          if (timing) {
+                            return `${count} ${intl.formatMessage({
+                              id: 'pages.getCaptchaSecondText',
+                              defaultMessage: '获取验证码',
+                            })}`;
+                          }
+                          return intl.formatMessage({
+                            id: 'pages.login.phoneLogin.getVerificationCode',
+                            defaultMessage: '获取验证码',
+                          });
+                        }}
+                        name="captcha"
+                        rules={[
+                          {
+                            required: true,
+                            message: (
+                              <FormattedMessage
+                                id="pages.login.captcha.required"
+                                defaultMessage="请输入验证码!"
+                              />
+                            ),
+                          },
+                        ]}
+                        onGetCaptcha={async (mobile) => {
+                          const result = await getCode(mobile);
+                          if (!result) {
+                            return;
+                          }
+                          message.success('获取验证码成功!请查看钉钉消息!');
+                        }}
+                      />
+                    </>
+                  )}
 
+                  {status === 'error' && loginType === 'mobile' && (
+                    <LoginMessage content="验证码错误" />
+                  )}
+                  {type === 'mobile' && (
+                    <>
+                      <ProFormText
+                        fieldProps={{
+                          size: 'large',
+                          prefix: <MobileOutlined />,
+                        }}
+                        name="mobile"
+                        placeholder={intl.formatMessage({
+                          id: 'pages.login.phoneNumber.placeholder',
+                          defaultMessage: '手机号',
+                        })}
+                        rules={[
+                          {
+                            required: true,
+                            message: (
+                              <FormattedMessage
+                                id="pages.login.phoneNumber.required"
+                                defaultMessage="请输入手机号!"
+                              />
+                            ),
+                          },
+                          {
+                            pattern: /^1\d{10}$/,
+                            message: (
+                              <FormattedMessage
+                                id="pages.login.phoneNumber.invalid"
+                                defaultMessage="手机号格式错误!"
+                              />
+                            ),
+                          },
+                        ]}
+                      />
+                      <ProFormCaptcha
+                        fieldProps={{
+                          size: 'large',
+                          prefix: <LockOutlined />,
+                        }}
+                        phoneName="mobile"
+                        captchaProps={{
+                          size: 'large',
+                        }}
+                        placeholder={intl.formatMessage({
+                          id: 'pages.login.captcha.placeholder',
+                          defaultMessage: '请输入验证码',
+                        })}
+                        captchaTextRender={(timing, count) => {
+                          if (timing) {
+                            return `${count} ${intl.formatMessage({
+                              id: 'pages.getCaptchaSecondText',
+                              defaultMessage: '获取验证码',
+                            })}`;
+                          }
+                          return intl.formatMessage({
+                            id: 'pages.login.phoneLogin.getVerificationCode',
+                            defaultMessage: '获取验证码',
+                          });
+                        }}
+                        name="captcha"
+                        rules={[
+                          {
+                            required: true,
+                            message: (
+                              <FormattedMessage
+                                id="pages.login.captcha.required"
+                                defaultMessage="请输入验证码!"
+                              />
+                            ),
+                          },
+                        ]}
+                        onGetCaptcha={async (mobile) => {
+                          const result = await getNoteCode(mobile);
+                          if (!result) {
+                            return;
+                          }
+                          message.success('获取验证码成功!请查看手机短信!');
+                        }}
+                      />
+                    </>
+                  )}
+                </LoginForm>
+              )}
+            </div>
+          </div>
         </div>
-      </div>
-    </div>}
-  </>
+      }
+    </>
+  );
 };
 
 export default Login;

+ 2 - 2
src/pages/User/Login/login.test.tsx

@@ -1,6 +1,6 @@
-import { render, fireEvent, act } from '@testing-library/react';
+import { TestBrowser } from '@@/testBrowser';
+import { act, fireEvent, render } from '@testing-library/react';
 import React from 'react';
-import { TestBrowser } from '@@/testBrowser';
 
 // @ts-ignore
 import { startMock } from '@@/requestRecordMock';

+ 129 - 132
src/pages/User/Login/style.less

@@ -1,11 +1,10 @@
-
 .container {
   position: fixed;
-  text-align: center;
   top: 0;
-  left: 0;
-  bottom: 0;
   right: 0;
+  bottom: 0;
+  left: 0;
+  text-align: center;
   background: linear-gradient(#1abc9c, transparent), linear-gradient(90deg, skyblue, transparent),
     linear-gradient(-90deg, coral, transparent);
   background-blend-mode: screen;
@@ -34,11 +33,11 @@
     // background-position: center 110px;
     // background-size: 100%;
     position: fixed;
-    text-align: center;
     top: 0;
-    left: 0;
-    bottom: 0;
     right: 0;
+    bottom: 0;
+    left: 0;
+    text-align: center;
     background: linear-gradient(#1abc9c, transparent), linear-gradient(90deg, skyblue, transparent),
       linear-gradient(-90deg, coral, transparent);
     background-blend-mode: screen;
@@ -85,19 +84,19 @@
 }
 
 .main {
-  transition: all 1.5s;
-  z-index: 999;
   position: fixed;
   top: 40%;
   left: 50%;
-  transform: translate(-50%, -50%);
-  width: 380px;
-  color: #fff;
-  justify-content: center;
-  align-items: center;
+  z-index: 999;
   display: flex;
   flex-flow: column;
+  align-items: center;
+  justify-content: center;
+  width: 380px;
+  color: #fff;
   font-size: 24px;
+  transform: translate(-50%, -50%);
+  transition: all 1.5s;
 
   // width: 368px;
   // margin: 0 auto;
@@ -110,15 +109,15 @@
     letter-spacing: 5px;
   }
 
-  >div {
+  > div {
     width: 300px;
     margin-bottom: 20px;
   }
 
   .code {
     display: flex;
-    justify-content: space-between;
     align-items: center;
+    justify-content: space-between;
 
     input {
       width: 170px;
@@ -129,25 +128,25 @@
     width: 300px;
     height: 42px;
     padding: 0 15px;
+    color: #fff;
+    font-size: 14px;
+    font-family: pt sans, Helvetica, Arial, sans-serif;
+    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
     background: #2d2d2d;
     background: rgba(45, 45, 45, 0.15);
+    border: 1px solid #3d3d3d;
+    border: 1px solid rgba(255, 255, 255, 0.15);
     -moz-border-radius: 6px;
     -webkit-border-radius: 6px;
     border-radius: 6px;
-    border: 1px solid #3d3d3d;
-    border: 1px solid rgba(255, 255, 255, 0.15);
+    outline: none;
     -moz-box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.1) inset;
     -webkit-box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.1) inset;
     box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.1) inset;
-    font-family: pt sans, Helvetica, Arial, sans-serif;
-    font-size: 14px;
-    color: #fff;
-    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
     -o-transition: all 0.2s;
     -moz-transition: all 0.2s;
     -webkit-transition: all 0.2s;
     -ms-transition: all 0.2s;
-    outline: none;
 
     &::-webkit-input-placeholder {
       color: #fff !important;
@@ -166,46 +165,46 @@
     }
   }
 
-  >button {
-    outline: none;
-    cursor: pointer;
+  > button {
     width: 300px;
     height: 44px;
     padding: 0;
+    color: #fff;
+    font-weight: 700;
+    font-size: 14px;
+    font-size: 20px;
+    font-family: pt sans, Helvetica, Arial, sans-serif;
+    letter-spacing: 10px;
+    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
     background: #22c50f;
+    border: 0px;
     -moz-border-radius: 6px;
     -webkit-border-radius: 6px;
     border-radius: 6px;
-    border: 0px;
+    outline: none;
     -moz-box-shadow: 0 15px 30px 0 rgba(255, 255, 255, 0.25) inset, 0 2px 7px 0 rgba(0, 0, 0, 0.2);
     -webkit-box-shadow: 0 15px 30px 0 rgba(255, 255, 255, 0.25) inset,
       0 2px 7px 0 rgba(0, 0, 0, 0.2);
     box-shadow: 0 15px 30px 0 rgba(255, 255, 255, 0.25) inset, 0 2px 7px 0 rgba(0, 0, 0, 0.2);
-    font-family: pt sans, Helvetica, Arial, sans-serif;
-    font-size: 14px;
-    font-weight: 700;
-    color: #fff;
-    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+    cursor: pointer;
     -o-transition: all 0.2s;
     -moz-transition: all 0.2s;
     -webkit-transition: all 0.2s;
     -ms-transition: all 0.2s;
-    font-size: 20px;
-    letter-spacing: 10px;
   }
 
   button:active {
+    border: 0 solid #ef4300;
     -moz-box-shadow: 0 15px 30px 0 rgba(255, 255, 255, 0.15) inset, 0 2px 7px 0 rgba(0, 0, 0, 0.2);
     -webkit-box-shadow: 0 15px 30px 0 rgba(255, 255, 255, 0.15) inset,
       0 2px 7px 0 rgba(0, 0, 0, 0.2);
     box-shadow: 0 5px 8px 0 rgba(0, 0, 0, 0.1) inset, 0 1px 4px 0 rgba(0, 0, 0, 0.1);
-    border: 0 solid #ef4300;
   }
 
   input:-internal-autofill-previewed,
   input:-internal-autofill-selected {
-    -webkit-text-fill-color: #ffffff !important;
     transition: background-color 5000s ease-in-out 0s !important;
+    -webkit-text-fill-color: #ffffff !important;
   }
 
   // .ant-input-affix-wrapper .ant-input:not(:first-child),
@@ -220,52 +219,50 @@
     box-shadow: 0 0 0px 1000px transparent inset !important;
   }
 }
-.btn{
-  outline: none;
-  cursor: pointer;
+.btn {
+  width: 35%;
+  height: 40px;
+  margin-left: 5%;
   padding: 0;
+  color: #fff;
+  font-weight: 700;
+  font-size: 14px;
+  font-family: pt sans, Helvetica, Arial, sans-serif;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
   background: #22c50f;
+  border: 0px;
   -moz-border-radius: 6px;
   -webkit-border-radius: 6px;
   border-radius: 6px;
-  border: 0px;
+  outline: none;
   -moz-box-shadow: 0 15px 30px 0 rgba(255, 255, 255, 0.25) inset, 0 2px 7px 0 rgba(0, 0, 0, 0.2);
-  -webkit-box-shadow: 0 15px 30px 0 rgba(255, 255, 255, 0.25) inset,
-    0 2px 7px 0 rgba(0, 0, 0, 0.2);
+  -webkit-box-shadow: 0 15px 30px 0 rgba(255, 255, 255, 0.25) inset, 0 2px 7px 0 rgba(0, 0, 0, 0.2);
   box-shadow: 0 15px 30px 0 rgba(255, 255, 255, 0.25) inset, 0 2px 7px 0 rgba(0, 0, 0, 0.2);
-  font-family: pt sans, Helvetica, Arial, sans-serif;
-  font-weight: 700;
-  color: #fff;
-  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+  cursor: pointer;
   -o-transition: all 0.2s;
   -moz-transition: all 0.2s;
   -webkit-transition: all 0.2s;
   -ms-transition: all 0.2s;
-  font-size: 14px;
-  width: 35%;
-  margin-left: 5%;
-  height: 40px;
 }
 button:active {
+  border: 0 solid #ef4300;
   -moz-box-shadow: 0 15px 30px 0 rgba(255, 255, 255, 0.15) inset, 0 2px 7px 0 rgba(0, 0, 0, 0.2);
-  -webkit-box-shadow: 0 15px 30px 0 rgba(255, 255, 255, 0.15) inset,
-    0 2px 7px 0 rgba(0, 0, 0, 0.2);
+  -webkit-box-shadow: 0 15px 30px 0 rgba(255, 255, 255, 0.15) inset, 0 2px 7px 0 rgba(0, 0, 0, 0.2);
   box-shadow: 0 5px 8px 0 rgba(0, 0, 0, 0.1) inset, 0 1px 4px 0 rgba(0, 0, 0, 0.1);
-  border: 0 solid #ef4300;
 }
 .company {
-  transition: all 1.5s;
-  z-index: 999;
   position: fixed;
   top: 40%;
   left: 50%;
-  transform: translate(-50%, -50%);
-  color: #fff;
-  justify-content: center;
-  align-items: center;
+  z-index: 999;
   display: flex;
   flex-flow: column;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
   font-size: 24px;
+  transform: translate(-50%, -50%);
+  transition: all 1.5s;
 
   h1 {
     color: #fff;
@@ -275,39 +272,41 @@ button:active {
 }
 
 .companyAccount {
-  border-radius: 8px;
-  background: rgba(255, 255, 255, .8);
-  box-shadow: 0 4px 20px 0 rgba(0, 60, 179, 0.12);
-  margin-bottom: 10px;
   width: 500px !important;
+  margin-top: 20px;
+  margin-bottom: 10px;
   padding: 48px 36px 10px;
   text-align: left;
-  margin-top: 20px;
+  background: rgba(255, 255, 255, 0.8);
+  border-radius: 8px;
+  box-shadow: 0 4px 20px 0 rgba(0, 60, 179, 0.12);
 
-  &>h3.title {
+  & > h3.title {
     margin: 0;
     font-size: 20px;
   }
 
-  &>.chooseTableBlock {
-    margin-top: 20px;
-    border-top: 1px solid rgba(181, 181, 181, .4);
-    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", "Source Han Sans CN", sans-serif;
+  & > .chooseTableBlock {
     max-height: 250px;
-    overflow-y: auto;
+    margin-top: 20px;
     padding: 0 10px;
+    overflow-y: auto;
+    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Ubuntu, 'Helvetica Neue',
+      Helvetica, Arial, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei UI', 'Microsoft YaHei',
+      'Source Han Sans CN', sans-serif;
+    border-top: 1px solid rgba(181, 181, 181, 0.4);
 
     .acTableLine {
-      padding: 16px 14px;
+      position: relative;
       display: flex;
       align-items: center;
-      font-size: 14px;
-      cursor: pointer;
-      color: #68779c;
       justify-content: space-between;
+      padding: 16px 14px;
+      color: #68779c;
+      font-size: 14px;
       border: 2px solid transparent;
-      position: relative;
       border-radius: 4px;
+      cursor: pointer;
 
       .actname {
         width: 250px;
@@ -317,12 +316,12 @@ button:active {
 
       .right {
         display: flex;
-        justify-content: flex-end;
         align-items: center;
+        justify-content: flex-end;
 
         .actcha {
-          color: #68779c;
           margin-right: 10px;
+          color: #68779c;
         }
       }
 
@@ -332,10 +331,10 @@ button:active {
       }
 
       &:hover {
-        border: 2px solid #296aef;
         z-index: 5;
-        transition: all .2s;
+        border: 2px solid #296aef;
         box-shadow: 0 0 12px rgba(0, 60, 179, 0.12);
+        transition: all 0.2s;
 
         .right .actcha {
           color: #0b1531;
@@ -347,41 +346,40 @@ button:active {
       }
 
       &::after {
-        content: '';
         position: absolute;
         bottom: 0;
         left: 0;
-        background-color: rgba(181, 181, 181, .4);
-        height: 1px;
         width: 100%;
+        height: 1px;
+        background-color: rgba(181, 181, 181, 0.4);
+        content: '';
       }
     }
   }
 
-  &>.button {
+  & > .button {
     margin-top: 20px;
     text-align: center;
   }
 }
 
-
 .bg {
   position: fixed;
-  left: 0;
   top: 0;
   right: 0;
   bottom: 0;
-  padding: 0;
+  left: 0;
   margin: 0;
+  padding: 0;
 
   li {
     position: absolute;
-    left: 0;
     top: 0;
+    left: 0;
     width: 100%;
     height: 100%;
-    transition: all 3s;
     opacity: 0;
+    transition: all 3s;
 
     &:first-child {
       opacity: 1;
@@ -398,14 +396,14 @@ button:active {
 .footer {
   position: fixed;
   bottom: 50px;
+  left: 50%;
   display: flex;
   flex-flow: column;
-  justify-content: center;
   align-items: center;
+  justify-content: center;
   color: #fff;
-  left: 50%;
-  transform: translateX(-50%);
   font-size: 20px;
+  transform: translateX(-50%);
   transition: all 1.5s;
 
   a {
@@ -413,7 +411,7 @@ button:active {
     letter-spacing: 10px;
   }
 
-  >span {
+  > span {
     span {
       margin: 0 3px;
       font-size: 15px;
@@ -423,18 +421,18 @@ button:active {
 
 .config {
   position: absolute;
-  bottom: 0%;
   right: 0%;
-  width: 300px;
+  bottom: 0%;
   z-index: 999;
-  padding: 0;
+  width: 300px;
+  height: 300px;
   margin: 0;
-  border-radius: 5px;
+  padding: 0;
   // transform: translate(-50%, -50%);
   color: #fff;
   font-size: 15px;
-  height: 300px;
-  background-color: rgba(0, 0, 0, .6);
+  background-color: rgba(0, 0, 0, 0.6);
+  border-radius: 5px;
   span {
     color: #fff;
     background-color: transparent;
@@ -450,25 +448,25 @@ button:active {
     width: 300px;
     height: 27px;
     padding: 0 15px;
+    color: #fff;
+    font-size: 14px;
+    font-family: pt sans, Helvetica, Arial, sans-serif;
+    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
     background: #2d2d2d;
     background: rgba(45, 45, 45, 0.15);
+    border: 1px solid #3d3d3d;
+    border: 1px solid rgba(255, 255, 255, 0.15);
     -moz-border-radius: 6px;
     -webkit-border-radius: 6px;
     border-radius: 6px;
-    border: 1px solid #3d3d3d;
-    border: 1px solid rgba(255, 255, 255, 0.15);
+    outline: none;
     -moz-box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.1) inset;
     -webkit-box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.1) inset;
     box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.1) inset;
-    font-family: pt sans, Helvetica, Arial, sans-serif;
-    font-size: 14px;
-    color: #fff;
-    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
     -o-transition: all 0.2s;
     -moz-transition: all 0.2s;
     -webkit-transition: all 0.2s;
     -ms-transition: all 0.2s;
-    outline: none;
 
     &::-webkit-input-placeholder {
       color: #fff !important;
@@ -489,17 +487,17 @@ button:active {
 
   a {
     display: inline-block;
-    font-size: 24px;
-    font-weight: bolder;
     margin: 0 10px;
-    text-decoration: none;
+    font-weight: bolder;
+    font-size: 24px;
     font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+    text-decoration: none;
   }
 
   li {
     display: flex;
-    justify-content: center;
     align-items: center;
+    justify-content: center;
     margin-top: 5px;
 
     input {
@@ -525,20 +523,20 @@ button:active {
 
 .config_qtww {
   position: fixed;
-  right: 10px;
   top: -28px;
+  right: 10px;
   z-index: 999;
   width: 100px;
   cursor: pointer;
- 
+
   img {
     width: 100%;
   }
-  >small{
-    animation:csshover 10s ease-in infinite;
+  > small {
+    animation: csshover 10s ease-in infinite;
   }
   &:hover {
-    >small {
+    > small {
       opacity: 1;
     }
   }
@@ -555,27 +553,26 @@ button:active {
 }
 
 .config_msg {
-  transition: all 0.8s;
-  opacity: 0;
+  position: absolute;
+  bottom: -40px;
+  left: -30px;
   float: left;
+  box-sizing: border-box;
   width: 200px;
   margin: 50px;
-  background-color: transparent;
-  border: 1px solid #fff;
+  padding: 5px 12px 5px 12px;
   /*为了给after伪元素自动继承*/
   color: #fff;
   font-size: 12px;
   line-height: 18px;
-  padding: 5px 12px 5px 12px;
-  box-sizing: border-box;
+  background-color: transparent;
+  border: 1px solid #fff;
   border-radius: 6px;
-  position: absolute;
-  left: -30px;
-  bottom: -40px;
   transform: translateX(-100%);
+  opacity: 0;
+  transition: all 0.8s;
 
   &::before {
-    content: '';
     position: absolute;
     top: 50%;
     right: -5px;
@@ -585,6 +582,7 @@ button:active {
     background: inherit;
     /*自动继承父元素的背景*/
     transform: rotate(45deg);
+    content: '';
     // border-top: 1px solid;
     // border-right: 1px solid;
   }
@@ -594,26 +592,25 @@ button:active {
   display: flex;
   justify-content: space-evenly;
 
-  >span {
+  > span {
     cursor: pointer;
   }
 }
 
 @keyframes csshover {
-  0%{
+  0% {
     opacity: 0;
   }
-  10%{
+  10% {
     opacity: 0.5;
   }
-  50%{
+  50% {
     opacity: 1;
   }
-  90%{
+  90% {
     opacity: 0.5;
   }
-  100%{
+  100% {
     opacity: 0;
   }
- }
- 
+}

+ 0 - 164
src/pages/Welcome.tsx

@@ -1,164 +0,0 @@
-import { PageContainer } from '@ant-design/pro-components';
-import { useModel } from '@umijs/max';
-import { Card, theme } from 'antd';
-import React from 'react';
-
-/**
- * 每个单独的卡片,为了复用样式抽成了组件
- * @param param0
- * @returns
- */
-const InfoCard: React.FC<{
-  title: string;
-  index: number;
-  desc: string;
-  href: string;
-}> = ({ title, href, index, desc }) => {
-  const { useAdmin-Token } = theme;
-
-  const { token } = useAdmin-Token();
-
-  return (
-    <div
-      style={{
-        backgroundColor: token.colorBgContainer,
-        boxShadow: token.boxShadow,
-        borderRadius: '8px',
-        fontSize: '14px',
-        color: token.colorTextSecondary,
-        lineHeight: '22px',
-        padding: '16px 19px',
-        minWidth: '220px',
-        flex: 1,
-      }}
-    >
-      <div
-        style={{
-          display: 'flex',
-          gap: '4px',
-          alignItems: 'center',
-        }}
-      >
-        <div
-          style={{
-            width: 48,
-            height: 48,
-            lineHeight: '22px',
-            backgroundSize: '100%',
-            textAlign: 'center',
-            padding: '8px 16px 16px 12px',
-            color: '#FFF',
-            fontWeight: 'bold',
-            backgroundImage:
-              "url('https://gw.alipayobjects.com/zos/bmw-prod/daaf8d50-8e6d-4251-905d-676a24ddfa12.svg')",
-          }}
-        >
-          {index}
-        </div>
-        <div
-          style={{
-            fontSize: '16px',
-            color: token.colorText,
-            paddingBottom: 8,
-          }}
-        >
-          {title}
-        </div>
-      </div>
-      <div
-        style={{
-          fontSize: '14px',
-          color: token.colorTextSecondary,
-          textAlign: 'justify',
-          lineHeight: '22px',
-          marginBottom: 8,
-        }}
-      >
-        {desc}
-      </div>
-      <a href={href} target="_blank" rel="noreferrer">
-        了解更多 {'>'}
-      </a>
-    </div>
-  );
-};
-
-const Welcome: React.FC = () => {
-  const { token } = theme.useAdmin-Token();
-  const { initialState } = useModel('@@initialState');
-  return (
-    <PageContainer>
-      <Card
-        style={{
-          borderRadius: 8,
-        }}
-        bodyStyle={{
-          backgroundImage:
-            initialState?.settings?.navTheme === 'realDark'
-              ? 'background-image: linear-gradient(75deg, #1A1B1F 0%, #191C1F 100%)'
-              : 'background-image: linear-gradient(75deg, #FBFDFF 0%, #F5F7FF 100%)',
-        }}
-      >
-        <div
-          style={{
-            backgroundPosition: '100% -30%',
-            backgroundRepeat: 'no-repeat',
-            backgroundSize: '274px auto',
-            backgroundImage:
-              "url('https://gw.alipayobjects.com/mdn/rms_a9745b/afts/img/A*BuFmQqsB2iAAAAAAAAAAAAAAARQnAQ')",
-          }}
-        >
-          <div
-            style={{
-              fontSize: '20px',
-              color: token.colorTextHeading,
-            }}
-          >
-            欢迎使用 Ant Design Pro
-          </div>
-          <p
-            style={{
-              fontSize: '14px',
-              color: token.colorTextSecondary,
-              lineHeight: '22px',
-              marginTop: 16,
-              marginBottom: 32,
-              width: '65%',
-            }}
-          >
-            Ant Design Pro 是一个整合了 umi,Ant Design 和 ProComponents
-            的脚手架方案。致力于在设计规范和基础组件的基础上,继续向上构建,提炼出典型模板/业务组件/配套设计资源,进一步提升企业级中后台产品设计研发过程中的『用户』和『设计者』的体验。
-          </p>
-          <div
-            style={{
-              display: 'flex',
-              flexWrap: 'wrap',
-              gap: 16,
-            }}
-          >
-            <InfoCard
-              index={1}
-              href="https://umijs.org/docs/introduce/introduce"
-              title="了解 umi"
-              desc="umi 是一个可扩展的企业级前端应用框架,umi 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。"
-            />
-            <InfoCard
-              index={2}
-              title="了解 ant design"
-              href="https://ant.design"
-              desc="antd 是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。"
-            />
-            <InfoCard
-              index={3}
-              title="了解 Pro Components"
-              href="https://procomponents.ant.design"
-              desc="ProComponents 是一个基于 Ant Design 做了更高抽象的模板组件,以 一个组件就是一个页面为开发理念,为中后台开发带来更好的体验。"
-            />
-          </div>
-        </div>
-      </Card>
-    </PageContainer>
-  );
-};
-
-export default Welcome;

+ 50 - 0
src/pages/book/bookManage/author/formConfig.tsx

@@ -0,0 +1,50 @@
+import { ProFormColumnsType } from '@ant-design/pro-components';
+
+function formConfig(): ProFormColumnsType<{
+  name: string;
+  state: string;
+}>[] {
+  return [
+    {
+      title: '作者名称',
+      dataIndex: 'authorName',
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      title: '手机号',
+      dataIndex: 'authorPhone',
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      title: '邮箱',
+      dataIndex: 'authorEmail',
+      formItemProps: {
+        style: { marginBottom: 10 },
+      },
+    },
+    {
+      //单本
+      title: '描述',
+      dataIndex: 'authorDesc',
+      valueType: 'textarea',
+    },
+  ];
+}
+
+export default formConfig;

+ 125 - 4
src/pages/book/bookManage/author/index.tsx

@@ -1,4 +1,125 @@
-function Page(){
-    return "作者管理"
-}
-export default Page
+import { useAjax } from '@/Hook/useAjax';
+import {
+  apibookAuthorAdd,
+  apibookAuthorDel,
+  apibookAuthorEdit,
+  apibookAuthorList,
+} from '@/services/book/author';
+import { PlusCircleOutlined } from '@ant-design/icons';
+import {
+  ActionType,
+  BetaSchemaForm,
+  PageContainer,
+  ProFormInstance,
+  ProTable,
+} from '@ant-design/pro-components';
+import { Button, message } from 'antd';
+import { useRef, useState } from 'react';
+import formConfig from './formConfig';
+import { columns } from './tableConfig';
+
+type DataItem = {
+  name: string;
+  state: string;
+};
+const Page: React.FC = () => {
+  const formRef = useRef<ProFormInstance>();
+  const actionRef = useRef<ActionType>();
+  let [open, setOpen] = useState<any>(null); //新增作者
+  let [editValues, setEditValues] = useState<any>({});
+  let bookAuthorList = useAjax((params) => apibookAuthorList(params), { type: 'table' });
+  let bookAuthorEdit = useAjax((params) => apibookAuthorEdit(params));
+  let bookAuthorAdd = useAjax((params) => apibookAuthorAdd(params));
+  let bookAuthorDel = useAjax((params) => apibookAuthorDel(params));
+
+  // 编辑
+  const edit = (item: any) => {
+    setEditValues(item);
+    setTimeout(() => {
+      formRef?.current?.setFieldsValue({ ...item });
+    }, 100);
+    setOpen(true);
+  };
+  // 删除
+  const del = (id: any) => {
+    bookAuthorDel.run(id).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('删除成功');
+      }
+    });
+  };
+  // 新增 or 编辑
+  const submit = async (values: any) => {
+    let api = editValues?.id ? bookAuthorEdit : bookAuthorAdd;
+    if (editValues?.id) {
+      values.id = editValues?.id;
+    }
+    api.run(values).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('操作成功!');
+        closeForm(false);
+      }
+    });
+    console.log(values);
+  };
+  // 关闭表单弹窗和重置表单内容
+  const closeForm = (b: boolean, values?: any) => {
+    if (!b) {
+      setEditValues({});
+      formRef?.current?.resetFields?.();
+      setOpen(b);
+    } else {
+    }
+  };
+  return (
+    <PageContainer>
+      <ProTable<any, any>
+        headerTitle={'作者列表'}
+        actionRef={actionRef}
+        rowKey={(r) => r.id}
+        search={{
+          labelWidth: 120,
+        }}
+        request={async (params) => {
+          return await bookAuthorList.run(params);
+        }}
+        scroll={{ x: 'auto' }}
+        toolBarRender={() => {
+          return [
+            <Button
+              type="primary"
+              onClick={() => {
+                setOpen(true);
+              }}
+            >
+              <PlusCircleOutlined />
+              新增作者
+            </Button>,
+          ];
+        }}
+        columns={columns(edit, del)}
+        // bordered
+      />
+      <BetaSchemaForm<DataItem>
+        title={!editValues?.id ? '新增作者' : '编辑作者'}
+        formRef={formRef}
+        width={600}
+        open={open}
+        onOpenChange={(b) => {
+          !b && closeForm(b);
+        }}
+        layoutType={'ModalForm'}
+        labelCol={{ span: 6 }}
+        wrapperCol={{ span: 14 }}
+        // grid={true}
+        layout="horizontal"
+        onFinish={submit}
+        columns={formConfig()}
+        loading={bookAuthorEdit?.loading || bookAuthorAdd?.loading}
+      />
+    </PageContainer>
+  );
+};
+export default Page;

+ 93 - 0
src/pages/book/bookManage/author/tableConfig.tsx

@@ -0,0 +1,93 @@
+import { ProColumns } from '@ant-design/pro-components';
+import { Button, Popconfirm, Space } from 'antd';
+
+export const columns = (edit: (params: any) => void, del: (id: any) => void): ProColumns<any>[] => {
+  return [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      key: 'id',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '创作者ID',
+      dataIndex: 'creatorUserId',
+      key: 'creatorUserId',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '作者名称',
+      dataIndex: 'authorName',
+      key: 'authorName',
+      width: 80,
+      ellipsis: true,
+    },
+    {
+      title: '作者描述',
+      dataIndex: 'authorDesc',
+      key: 'authorDesc',
+      width: 80,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '手机',
+      dataIndex: 'authorPhone',
+      key: 'authorPhone',
+      align: 'center',
+      width: 80,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '邮箱',
+      dataIndex: 'authorEmail',
+      key: 'authorEmail',
+      align: 'center',
+      width: 120,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '操作',
+      dataIndex: 'cz',
+      key: 'cz',
+      width: 160,
+      align: 'center',
+      className: 'table_fixed',
+      hideInSearch: true,
+      render: (a: any, b: any) => {
+        return (
+          <Space>
+            <Button
+              onClick={() => {
+                edit(b);
+              }}
+              type="link"
+            >
+              编辑
+            </Button>
+            <Popconfirm
+              title={
+                <div>
+                  确定要删除<span style={{ color: 'red' }}>{b.name}</span>分类?
+                </div>
+              }
+              onConfirm={() => {
+                del(b.id);
+              }}
+            >
+              <a style={{ color: 'red', cursor: 'pointer' }}>删除</a>
+            </Popconfirm>
+          </Space>
+        );
+      },
+    },
+  ];
+};

+ 65 - 0
src/pages/book/bookManage/bookTags/formConfig.tsx

@@ -0,0 +1,65 @@
+import { ProFormColumnsType } from '@ant-design/pro-components';
+
+function formConfig(enumList?: { [key: string]: any }): ProFormColumnsType<{
+  name: string;
+  state: string;
+}>[] {
+  return [
+    {
+      title: '所属频道',
+      dataIndex: 'workDirection',
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+      valueEnum: () => {
+        let arr = enumList?.WORK_DIRECTION?.values;
+        return arr
+          ? new Map([...arr]?.map(({ value, description }: any) => [value, description]))
+          : {};
+      },
+    },
+    {
+      title: '标签名',
+      dataIndex: 'name',
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      title: '排序',
+      dataIndex: 'sortNum',
+      valueType: 'digit',
+      formItemProps: {
+        style: { marginBottom: 10 },
+      },
+    },
+    {
+      //单本
+      title: '热门',
+      dataIndex: 'isHot',
+      valueType: 'radio',
+      valueEnum: () => {
+        return new Map(
+          [
+            { value: 0, description: '关闭' },
+            { value: 1, description: '开启' },
+          ]?.map(({ value, description }: any) => [value, description]),
+        );
+      },
+    },
+  ];
+}
+
+export default formConfig;

+ 164 - 4
src/pages/book/bookManage/bookTags/index.tsx

@@ -1,4 +1,164 @@
-function Page(){
-    return "标签管理"
-}
-export default Page
+import { useAjax } from '@/Hook/useAjax';
+import {
+  apiBookContentLabelAdd,
+  apiBookContentLabelDel,
+  apiBookContentLabelEdit,
+  apiBookContentLabelHot,
+  apiBookContentLabelList,
+  apiBookContentLabelSort,
+} from '@/services/book/bookTags';
+import { PlusCircleOutlined } from '@ant-design/icons';
+import {
+  ActionType,
+  BetaSchemaForm,
+  PageContainer,
+  ProFormInstance,
+  ProTable,
+} from '@ant-design/pro-components';
+import { useModel } from '@umijs/max';
+import { Button, message } from 'antd';
+import { useRef, useState } from 'react';
+import formConfig from './formConfig';
+import { columns } from './tableConfig';
+
+type DataItem = {
+  name: string;
+  state: string;
+};
+const Page: React.FC = () => {
+  const formRef = useRef<ProFormInstance>();
+  const actionRef = useRef<ActionType>();
+  let { state } = useModel('global');
+  let [open, setOpen] = useState<any>(null); //新增作者
+  let [editValues, setEditValues] = useState<any>({});
+  let BookContentLabelList = useAjax((params) => apiBookContentLabelList(params), {
+    type: 'table',
+  });
+  let BookContentLabelEdit = useAjax((params) => apiBookContentLabelEdit(params));
+  let BookContentLabelAdd = useAjax((params) => apiBookContentLabelAdd(params));
+  let BookContentLabelDel = useAjax((params) => apiBookContentLabelDel(params));
+  let BookContentLabelHot = useAjax((params) => apiBookContentLabelHot(params));
+  let BookContentLabelSort = useAjax((params) => apiBookContentLabelSort(params));
+  // 编辑
+  const edit = (item: any) => {
+    setEditValues(item);
+    setTimeout(() => {
+      formRef?.current?.setFieldsValue({ ...item });
+    }, 100);
+    setOpen(true);
+  };
+  // 删除
+  const del = (id: any) => {
+    BookContentLabelDel.run(id).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('删除成功');
+      }
+    });
+  };
+  // 新增 or 编辑
+  const submit = async (values: any) => {
+    let api = editValues?.id ? BookContentLabelEdit : BookContentLabelAdd;
+    if (editValues?.id) {
+      values.id = editValues?.id;
+    }
+    api.run(values).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('操作成功!');
+        closeForm(false);
+      }
+    });
+    console.log(values);
+  };
+  // 关闭表单弹窗和重置表单内容
+  const closeForm = (b: boolean, values?: any) => {
+    if (!b) {
+      setEditValues({});
+      formRef?.current?.resetFields?.();
+      setOpen(b);
+    } else {
+    }
+  };
+  //热门
+  const hot = (params: any) => {
+    return BookContentLabelHot.run(params)
+      .then((res: any) => {
+        if (!res?.code) {
+          return false;
+        } else {
+          actionRef?.current?.reload();
+          message.success('修改热门状态成功');
+          return true;
+        }
+      })
+      .catch((err: any) => {
+        return false;
+      });
+  };
+  // 排序
+  const sort = (params: any) => {
+    return BookContentLabelSort.run(params)
+      .then((res: any) => {
+        if (!res?.code) {
+          return false;
+        } else {
+          actionRef?.current?.reload();
+          message.success('修改排序成功');
+          return true;
+        }
+      })
+      .catch((err: any) => {
+        return false;
+      });
+  };
+  return (
+    <PageContainer>
+      <ProTable<any, any>
+        headerTitle={'标签列表'}
+        actionRef={actionRef}
+        rowKey={(r) => r.id}
+        search={{
+          labelWidth: 120,
+        }}
+        request={async (params) => {
+          return await BookContentLabelList.run(params);
+        }}
+        scroll={{ x: 'auto' }}
+        toolBarRender={() => {
+          return [
+            <Button
+              type="primary"
+              onClick={() => {
+                setOpen(true);
+              }}
+            >
+              <PlusCircleOutlined />
+              新增标签
+            </Button>,
+          ];
+        }}
+        columns={columns({ edit, del, enumList: state?.enumList, hot, sort })}
+        // bordered
+      />
+      <BetaSchemaForm<DataItem>
+        title={!editValues?.id ? '新增标签' : '编辑标签'}
+        formRef={formRef}
+        width={600}
+        open={open}
+        onOpenChange={(b) => {
+          !b && closeForm(b);
+        }}
+        layoutType={'ModalForm'}
+        labelCol={{ span: 6 }}
+        wrapperCol={{ span: 14 }}
+        // grid={true}
+        layout="horizontal"
+        onFinish={submit}
+        columns={formConfig(state?.enumList)}
+        loading={BookContentLabelEdit?.loading || BookContentLabelAdd?.loading}
+      />
+    </PageContainer>
+  );
+};
+export default Page;

+ 174 - 0
src/pages/book/bookManage/bookTags/tableConfig.tsx

@@ -0,0 +1,174 @@
+import { MyIcon } from '@/global';
+import { EditOutlined } from '@ant-design/icons';
+import { ProColumns } from '@ant-design/pro-components';
+import { Button, Input, Popconfirm, Space } from 'antd';
+import { useState } from 'react';
+
+export const columns = (params: {
+  sort: (params: { id: any; sortNum: any }) => void;
+  edit: (params: any) => void;
+  del: (id: any) => void;
+  enumList?: { [key: string]: any };
+  hot: (params: { id: any; isHot: any }) => void;
+}): ProColumns<any>[] => {
+  let { edit, del, enumList, hot, sort } = params;
+  return [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      key: 'id',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '标签名称',
+      dataIndex: 'name',
+      key: 'name',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+    },
+    {
+      title: '所属频道',
+      dataIndex: 'workDirection',
+      key: 'workDirection',
+      align: 'center',
+      width: 80,
+      ellipsis: true,
+      valueEnum: () => {
+        let arr = enumList?.WORK_DIRECTION?.values;
+        return arr
+          ? new Map(
+              [{ value: '', description: '全部' }, ...arr]?.map(({ value, description }: any) => [
+                value,
+                description,
+              ]),
+            )
+          : {};
+      },
+    },
+    {
+      title: '热门标签',
+      dataIndex: 'isHot',
+      key: 'isHot',
+      width: 80,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      tooltip: '点击可直接更改状态',
+      valueEnum: (row) => ({
+        1: {
+          text: (
+            <div
+              style={{ cursor: 'pointer' }}
+              onClick={() => {
+                hot({ id: row.id, isHot: row?.isHot == 0 ? 1 : 0 });
+              }}
+            >
+              {' '}
+              是<MyIcon type="icon-remen" />{' '}
+            </div>
+          ),
+        },
+        0: {
+          text: (
+            <div
+              style={{ cursor: 'pointer' }}
+              onClick={() => {
+                hot({ id: row.id, isHot: row?.isHot == 0 ? 1 : 0 });
+              }}
+            >
+              否<MyIcon type="icon-remen-d" />
+            </div>
+          ),
+        },
+      }),
+    },
+    {
+      title: '排序',
+      dataIndex: 'sortNum',
+      key: 'sortNum',
+      width: 80,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+      render: (text, record, _, action) => {
+        function Page({ sortNum }: any) {
+          const [value, setValue] = useState(sortNum);
+          return (
+            <Popconfirm
+              title={
+                <Input
+                  addonBefore="移动到第"
+                  addonAfter="位"
+                  style={{ width: 180 }}
+                  value={value}
+                  onChange={(e) => {
+                    let v = e.target.value;
+                    if (!v) {
+                      v = '0';
+                    }
+                    let ok = /^\d+$/.test(v);
+                    if (ok) {
+                      setValue(Number(v));
+                    }
+                  }}
+                />
+              }
+              onConfirm={() => {
+                sort({ id: record.id, sortNum: value });
+              }}
+              onCancel={() => {
+                setValue(sortNum);
+              }}
+              icon={false}
+            >
+              <Button type="link">
+                {text}
+                <EditOutlined />
+              </Button>
+            </Popconfirm>
+          );
+        }
+        return <Page sortNum={record.sortNum} />;
+      },
+    },
+    {
+      title: '操作',
+      dataIndex: 'option',
+      key: 'option',
+      width: 160,
+      align: 'center',
+      className: 'table_fixed',
+      hideInSearch: true,
+      render: (a: any, b: any, _, action) => {
+        return (
+          <Space>
+            <Button
+              onClick={() => {
+                edit(b);
+              }}
+              type="link"
+            >
+              编辑
+            </Button>
+            <Popconfirm
+              title={
+                <div>
+                  确定要删除<span style={{ color: 'red' }}>{b.name}</span>分类?
+                </div>
+              }
+              onConfirm={() => {
+                del(b.id);
+              }}
+            >
+              <a style={{ color: 'red', cursor: 'pointer' }}>删除</a>
+            </Popconfirm>
+          </Space>
+        );
+      },
+    },
+  ];
+};

+ 51 - 0
src/pages/book/bookManage/classifyList/formConfig.tsx

@@ -0,0 +1,51 @@
+import { ProFormColumnsType } from '@ant-design/pro-components';
+
+function formConfig(enumList?: { [key: string]: any }): ProFormColumnsType<{
+  name: string;
+  state: string;
+}>[] {
+  return [
+    {
+      title: '所属频道',
+      dataIndex: 'workDirection',
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+      valueEnum: () => {
+        let arr = enumList?.WORK_DIRECTION?.values;
+        return arr
+          ? new Map([...arr]?.map(({ value, description }: any) => [value, description]))
+          : {};
+      },
+    },
+    {
+      title: '分类名',
+      dataIndex: 'name',
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      title: '排序',
+      dataIndex: 'sortNum',
+      valueType: 'digit',
+      formItemProps: {
+        style: { marginBottom: 10 },
+      },
+    },
+  ];
+}
+
+export default formConfig;

+ 145 - 4
src/pages/book/bookManage/classifyList/index.tsx

@@ -1,4 +1,145 @@
-function Page(){
-    return "分类管理"
-}
-export default Page
+import { useAjax } from '@/Hook/useAjax';
+import {
+  apibookCategoryAdd,
+  apibookCategoryDel,
+  apibookCategoryEdit,
+  apibookCategoryList,
+  apibookCategorySort,
+} from '@/services/book/classifyList';
+import { PlusCircleOutlined } from '@ant-design/icons';
+import {
+  ActionType,
+  BetaSchemaForm,
+  PageContainer,
+  ProFormInstance,
+  ProTable,
+} from '@ant-design/pro-components';
+import { useModel } from '@umijs/max';
+import { Button, message } from 'antd';
+import { useRef, useState } from 'react';
+import formConfig from './formConfig';
+import { columns } from './tableConfig';
+
+type DataItem = {
+  name: string;
+  state: string;
+};
+const Page: React.FC = () => {
+  const formRef = useRef<ProFormInstance>();
+  const actionRef = useRef<ActionType>();
+  let { state } = useModel('global');
+  let [open, setOpen] = useState<any>(null); //新增作者
+  let [editValues, setEditValues] = useState<any>({});
+  let bookCategoryList = useAjax((params) => apibookCategoryList(params), { type: 'table' });
+  let bookCategorySort = useAjax((params) => apibookCategorySort(params));
+  let bookCategoryEdit = useAjax((params) => apibookCategoryEdit(params));
+  let bookCategoryAdd = useAjax((params) => apibookCategoryAdd(params));
+  let bookCategoryDel = useAjax((params) => apibookCategoryDel(params));
+  // 编辑
+  const edit = (item: any) => {
+    setEditValues(item);
+    setTimeout(() => {
+      formRef?.current?.setFieldsValue({ ...item });
+    }, 100);
+    setOpen(true);
+  };
+  // 删除
+  const del = (id: any) => {
+    bookCategoryDel.run(id).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('删除成功');
+      }
+    });
+  };
+  // 新增 or 编辑
+  const submit = async (values: any) => {
+    let api = editValues?.id ? bookCategoryEdit : bookCategoryAdd;
+    if (editValues?.id) {
+      values.id = editValues?.id;
+    }
+    api.run(values).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('操作成功!');
+        closeForm(false);
+      }
+    });
+    console.log(values);
+  };
+  // 关闭表单弹窗和重置表单内容
+  const closeForm = (b: boolean, values?: any) => {
+    if (!b) {
+      setEditValues({});
+      formRef?.current?.resetFields?.();
+      setOpen(b);
+    } else {
+    }
+  };
+  // 排序
+  const sort = (params: any) => {
+    return bookCategorySort
+      .run(params)
+      .then((res: any) => {
+        if (!res?.code) {
+          return false;
+        } else {
+          actionRef?.current?.reload();
+          message.success('修改排序成功');
+          return true;
+        }
+      })
+      .catch((err: any) => {
+        return false;
+      });
+  };
+  return (
+    <PageContainer>
+      <ProTable<any, any>
+        headerTitle={'分类列表'}
+        actionRef={actionRef}
+        rowKey={(r) => r.id}
+        search={{
+          labelWidth: 120,
+        }}
+        scroll={{ x: 'auto' }}
+        request={async (params) => {
+          return await bookCategoryList.run(params);
+        }}
+        toolBarRender={() => {
+          return [
+            <Button
+              type="primary"
+              onClick={() => {
+                setOpen(true);
+              }}
+            >
+              <PlusCircleOutlined />
+              新增分类
+            </Button>,
+          ];
+        }}
+        columns={columns({ edit, del, enumList: state?.enumList, sort })}
+        // bordered
+      />
+      <BetaSchemaForm<DataItem>
+        title={!editValues?.id ? '新增分类' : '编辑分类'}
+        formRef={formRef}
+        width={600}
+        open={open}
+        onOpenChange={(b) => {
+          !b && closeForm(b);
+        }}
+        layoutType={'ModalForm'}
+        labelCol={{ span: 6 }}
+        wrapperCol={{ span: 14 }}
+        // grid={true}
+        layout="horizontal"
+        onFinish={submit}
+        columns={formConfig(state?.enumList)}
+        loading={bookCategoryEdit?.loading || bookCategoryAdd?.loading}
+      />
+    </PageContainer>
+  );
+};
+export default Page;

+ 135 - 0
src/pages/book/bookManage/classifyList/tableConfig.tsx

@@ -0,0 +1,135 @@
+import { EditOutlined } from '@ant-design/icons';
+import { ProColumns } from '@ant-design/pro-components';
+import { Button, Input, Popconfirm, Space } from 'antd';
+import { useState } from 'react';
+
+export const columns = (params: {
+  sort: (params: { id: any; sortNum: any }) => void;
+  edit: (params: any) => void;
+  del: (id: any) => void;
+  enumList?: { [key: string]: any };
+}): ProColumns<any>[] => {
+  let { edit, del, enumList, sort } = params;
+  return [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      key: 'id',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '分类名称',
+      dataIndex: 'name',
+      key: 'name',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+    },
+    {
+      title: '所属频道',
+      dataIndex: 'workDirection',
+      key: 'workDirection',
+      align: 'center',
+      width: 80,
+      ellipsis: true,
+      valueEnum: () => {
+        let arr = enumList?.WORK_DIRECTION?.values;
+        return arr
+          ? new Map(
+              [{ value: '', description: '全部' }, ...arr]?.map(({ value, description }: any) => [
+                value,
+                description,
+              ]),
+            )
+          : {};
+      },
+    },
+    {
+      title: '排序',
+      dataIndex: 'sortNum',
+      key: 'sortNum',
+      width: 80,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+      render: (text, record, _, action) => {
+        function Page({ sortNum }: any) {
+          const [value, setValue] = useState(sortNum);
+          return (
+            <Popconfirm
+              title={
+                <Input
+                  addonBefore="移动到第"
+                  addonAfter="位"
+                  style={{ width: 180 }}
+                  value={value}
+                  onChange={(e) => {
+                    let v = e.target.value;
+                    if (!v) {
+                      v = '0';
+                    }
+                    let ok = /^\d+$/.test(v);
+                    if (ok) {
+                      setValue(Number(v));
+                    }
+                  }}
+                />
+              }
+              onConfirm={() => {
+                sort({ id: record.id, sortNum: value });
+              }}
+              onCancel={() => {
+                setValue(sortNum);
+              }}
+              icon={false}
+            >
+              <Button type="link">
+                {text}
+                <EditOutlined />
+              </Button>
+            </Popconfirm>
+          );
+        }
+        return <Page sortNum={record.sortNum} />;
+      },
+    },
+    {
+      title: '操作',
+      dataIndex: 'option',
+      key: 'option',
+      width: 160,
+      align: 'center',
+      className: 'table_fixed',
+      hideInSearch: true,
+      render: (a: any, b: any, _, action) => {
+        return (
+          <Space>
+            <Button
+              onClick={() => {
+                edit(b);
+              }}
+              type="link"
+            >
+              编辑
+            </Button>
+            <Popconfirm
+              title={
+                <div>
+                  确定要删除<span style={{ color: 'red' }}>{b.name}</span>分类?
+                </div>
+              }
+              onConfirm={() => {
+                del(b.id);
+              }}
+            >
+              <a style={{ color: 'red', cursor: 'pointer' }}>删除</a>
+            </Popconfirm>
+          </Space>
+        );
+      },
+    },
+  ];
+};

+ 280 - 0
src/pages/book/bookManage/longbookList/formConfig.tsx

@@ -0,0 +1,280 @@
+import UploadImg from '@/components/uploadImg';
+import { ProFormColumnsType } from '@ant-design/pro-components';
+
+function formConfig(props: {
+  authList?: any[];
+  enumList?: { [key: string]: any };
+  labelList?: any[];
+  categoryList?: any[];
+}): ProFormColumnsType<{
+  name: string;
+  state: string;
+}>[] {
+  let { enumList, labelList, categoryList, authList } = props;
+  return [
+    {
+      valueType: 'group',
+      colProps: {
+        span: 12,
+      },
+      rowProps: { gutter: [14, 14] },
+      columns: [
+        {
+          title: '频道',
+          dataIndex: 'workDirection',
+          valueType: 'select',
+          fieldProps: { showSearch: true, placeholder: '请选择频道' },
+          colProps: {
+            span: 24,
+          },
+          formItemProps: {
+            style: { marginBottom: 10 },
+            rules: [
+              {
+                required: true,
+                message: '此项为必填项',
+              },
+            ],
+          },
+          valueEnum: () => {
+            let arr = enumList?.WORK_DIRECTION?.values;
+            return new Map(arr?.map(({ value, description }: any) => [value, description]));
+          },
+        },
+        {
+          valueType: 'dependency',
+          name: ['workDirection'],
+          columns: ({ workDirection }) => {
+            console.log('workDirection', workDirection);
+            return [
+              {
+                title: '分类',
+                dataIndex: 'categoryId',
+                valueType: 'select',
+                fieldProps: {
+                  showSearch: true,
+                  placeholder: '请选择分类',
+                  disabled: workDirection != 0 && workDirection != 1,
+                },
+                colProps: {
+                  span: 24,
+                },
+                formItemProps: {
+                  style: { marginBottom: 10 },
+                  rules: [
+                    {
+                      required: true,
+                      message: '此项为必填项',
+                    },
+                  ],
+                },
+                valueEnum: () => {
+                  let arr = categoryList || [];
+                  return new Map([...arr]?.map((item) => [item.id, item.name]));
+                },
+              },
+              {
+                title: '标签',
+                dataIndex: 'labelIds',
+                valueType: 'select',
+                colProps: {
+                  span: 24,
+                },
+                formItemProps: {
+                  style: { marginBottom: 10 },
+                  rules: [
+                    {
+                      required: true,
+                      message: '此项为必填项',
+                    },
+                  ],
+                },
+                convertValue: (value) => {
+                  let arr =
+                    typeof value === 'string' ? value?.split(',')?.map((s) => Number(s)) : value;
+                  return arr;
+                },
+                transform: (value) => {
+                  return typeof value != 'string' ? value.join() : value;
+                },
+                fieldProps: {
+                  mode: 'multiple',
+                  showSearch: true,
+                  placeholder: '请选择标签',
+                  disabled: workDirection != 0 && workDirection != 1,
+                },
+                valueEnum: () => {
+                  let arr = labelList || [];
+                  return new Map([...arr]?.map((item) => [item.id, item.name]));
+                },
+              },
+            ];
+          },
+        },
+        {
+          title: '书名',
+          dataIndex: 'bookName',
+          colProps: {
+            span: 24,
+          },
+          fieldProps: { placeholder: '请输入小说名称' },
+          formItemProps: {
+            style: { marginBottom: 10 },
+            rules: [
+              {
+                required: true,
+                message: '此项为必填项',
+              },
+            ],
+          },
+        },
+        {
+          title: '作者',
+          dataIndex: 'authorId',
+          valueType: 'select',
+          fieldProps: { showSearch: true, placeholder: '请选择作者' },
+          colProps: {
+            span: 24,
+          },
+          formItemProps: {
+            style: { marginBottom: 10 },
+            rules: [
+              {
+                required: true,
+                message: '此项为必填项',
+              },
+            ],
+          },
+          valueEnum: new Map(authList?.map(({ id, authorName }) => [id, authorName])),
+        },
+        {
+          title: '来源',
+          dataIndex: 'source',
+          valueType: 'select',
+          fieldProps: { showSearch: true, placeholder: '请选择来源' },
+          colProps: {
+            span: 24,
+          },
+          formItemProps: {
+            style: { marginBottom: 10 },
+            rules: [
+              {
+                required: true,
+                message: '此项为必填项',
+              },
+            ],
+          },
+          valueEnum: () => {
+            let arr = enumList?.SOURCE?.values;
+            return arr
+              ? new Map([...arr]?.map(({ value, description }: any) => [value, description]))
+              : {};
+          },
+        },
+        {
+          title: '描述',
+          dataIndex: 'bookDesc',
+          valueType: 'textarea',
+          colProps: {
+            span: 24,
+          },
+          fieldProps: { placeholder: '请输入小说描述' },
+          formItemProps: {
+            style: { marginBottom: 10 },
+            rules: [
+              {
+                required: true,
+                message: '此项为必填项',
+              },
+            ],
+          },
+        },
+      ],
+    },
+    {
+      valueType: 'group',
+      colProps: {
+        span: 12,
+      },
+      rowProps: { gutter: [14, 14] },
+      columns: [
+        {
+          title: '书籍状态',
+          dataIndex: 'bookStatus',
+          valueType: 'select',
+          colProps: {
+            span: 24,
+          },
+          formItemProps: {
+            style: { marginBottom: 10 },
+            rules: [
+              {
+                required: true,
+                message: '此项为必填项',
+              },
+            ],
+          },
+          valueEnum: () => {
+            let arr = enumList?.BOOK_STATUS?.values;
+            return new Map(arr?.map(({ value, description }: any) => [value, description]));
+          },
+        },
+        {
+          title: '上架状态',
+          dataIndex: 'shelveStatus',
+          valueType: 'select',
+          colProps: {
+            span: 24,
+          },
+          formItemProps: {
+            style: { marginBottom: 10 },
+            rules: [
+              {
+                required: true,
+                message: '此项为必填项',
+              },
+            ],
+          },
+          valueEnum: () => {
+            let arr = enumList?.SHELVE_STATUS?.values;
+            return new Map(arr?.map(({ value, description }: any) => [value, description]));
+          },
+        },
+        {
+          title: '封面',
+          dataIndex: 'picUrl',
+          colProps: {
+            span: 24,
+          },
+          formItemProps: {
+            style: { marginBottom: 10 },
+            rules: [
+              {
+                required: true,
+                message: '此项为必填项',
+              },
+            ],
+          },
+          renderFormItem: () => {
+            return (
+              <UploadImg
+                size={{
+                  width: 300,
+                  height: 400,
+                  evalW: '!=',
+                  evalH: '!=',
+                  msg: '需要图片大小300*400',
+                }}
+                isEdit={true}
+                type="image"
+                isCropper
+              />
+            );
+          },
+        },
+      ],
+    },
+  ];
+}
+
+export default formConfig;

+ 338 - 4
src/pages/book/bookManage/longbookList/index.tsx

@@ -1,4 +1,338 @@
-function Page(){
-    return "长篇小说"
-}
-export default Page
+import ReadBook from '@/components/readBook';
+import { useAjax } from '@/Hook/useAjax';
+import {
+  apiBookContentLongBookInfoAdd,
+  apiBookContentLongBookInfoBatch,
+  apiBookContentLongBookInfoDel,
+  apiBookContentLongBookInfoDels,
+  apiBookContentLongBookInfoEdit,
+  apiBookContentLongBookInfoList,
+  apiLongBookParagraph,
+} from '@/services/book/longbookList';
+import { apiLongBookInfoChapterAllList } from '@/services/distribution/longBook';
+import { ColumnHeightOutlined, DeleteOutlined, PlusCircleOutlined } from '@ant-design/icons';
+import {
+  ActionType,
+  BetaSchemaForm,
+  PageContainer,
+  ProFormInstance,
+  ProTable,
+} from '@ant-design/pro-components';
+import { useModel } from '@umijs/max';
+import { Button, Drawer, message, Popconfirm, Space } from 'antd';
+import { useEffect, useRef, useState } from 'react';
+import formConfig from './formConfig';
+import Item from './item';
+import { columns } from './tableConfig';
+const wordCountRanges: any = {
+  '': { start: null, end: null }, // 全部
+  '0-2': { start: 0, end: 2 * 10000 },
+  '2-5': { start: 2 * 10000, end: 5 * 10000 },
+  '5-10': { start: 5 * 10000, end: 10 * 10000 },
+  '10-20': { start: 10 * 10000, end: 20 * 10000 },
+  '20-40': { start: 20 * 10000, end: 40 * 10000 },
+  '40-100': { start: 40 * 10000, end: 100 * 10000 },
+  '100-150': { start: 100 * 10000, end: 150 * 10000 },
+  '150-200': { start: 150 * 10000, end: 200 * 10000 },
+  '200-300': { start: 200 * 10000, end: 300 * 10000 },
+  '300-0': { start: 300 * 10000, end: null }, // 300万以上
+};
+type DataItem = {
+  name: string;
+  state: string;
+};
+const Page: React.FC = () => {
+  let { state, getLabelAndClassList } = useModel('global');
+  let [open, setOpen] = useState<any>(null); //付费配置
+  let [editValues, setEditValues] = useState<any>({});
+  let [workDirection, setWorkDirection] = useState<any>(null);
+  const [openBook, setOpneBook] = useState<any>(null); //阅读小说
+  const [editSelectedRow, setEditSelectedRow] = useState<any[]>([]); //小说列表选择
+
+  // ======================API=======================
+  let BookContentLongBookInfoList = useAjax((params) => apiBookContentLongBookInfoList(params), {
+    type: 'table',
+  }); //获取书籍列表
+  let BookContentLongBookInfoAdd = useAjax((params) => apiBookContentLongBookInfoAdd(params)); //新增书籍
+  let BookContentLongBookInfoEdit = useAjax((params) => apiBookContentLongBookInfoEdit(params)); //编辑书籍
+  let BookContentLongBookInfoDel = useAjax((id) => apiBookContentLongBookInfoDel(id)); //单个删除书籍
+  let BookContentLongBookInfoDels = useAjax((ids) => apiBookContentLongBookInfoDels(ids)); //批量删除书籍
+  let BookContentLongBookInfoBatch = useAjax((params) => apiBookContentLongBookInfoBatch(params)); //批量上下架
+  let LongBookParagraph = useAjax((id) => apiLongBookParagraph(id)); //查询单个章节
+  let LongBookInfoChapterAllList = useAjax((id) => apiLongBookInfoChapterAllList(id)); //全部章节无分页
+  const formRef = useRef<ProFormInstance>();
+  const actionRef = useRef<ActionType>();
+
+  // 获取标签和分类
+  useEffect(() => {
+    getLabelAndClassList({ workDirection });
+  }, [workDirection]);
+  // 看小说列表
+  const lookBookList = (params: any) => {
+    let { id } = params;
+    return LongBookInfoChapterAllList.run(id).then((res) => {
+      setOpneBook({ list: res.data, ...params });
+    });
+  };
+  // 看小说
+  const lookBook = (params: any) => {
+    let { chapterNo } = params;
+    return LongBookParagraph.run(chapterNo).then((res) => {
+      // setOpneBook({ ...res.data, ...params })
+      return res.data;
+    });
+  };
+  // 删除or批量删除
+  const del = (ids: any) => {
+    let api = Array.isArray(ids) ? BookContentLongBookInfoDels : BookContentLongBookInfoDel;
+    api.run(ids).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('删除成功');
+        setEditSelectedRow([]);
+      }
+    });
+  };
+  // 批量上下架
+  const shelveAll = (ids: any[], shelve: 0 | 1) => {
+    BookContentLongBookInfoBatch.run({ ids, shelve }).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('批量' + (shelve === 0 ? '上架成功' : '下架成功'));
+      }
+    });
+  };
+  // 提交表单
+  const submit = async (values: any) => {
+    let api = editValues?.id ? BookContentLongBookInfoEdit : BookContentLongBookInfoAdd;
+    if (editValues?.id) {
+      values.id = editValues?.id;
+    }
+    api.run(values).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('操作成功!');
+        closeForm(false);
+      }
+    });
+  };
+  // 关闭表单弹窗和重置表单内容
+  const closeForm = (b: boolean, values?: any) => {
+    if (!b) {
+      setEditValues({});
+      formRef?.current?.resetFields?.();
+      setOpen(b);
+    } else {
+      setEditValues(values);
+      setWorkDirection(values.workDirection);
+      setTimeout(() => {
+        formRef?.current?.setFieldsValue(values);
+      }, 100);
+      setOpen(b);
+    }
+  };
+  return (
+    <PageContainer title={false} tabProps={{ type: 'card' }}>
+      <ProTable<any, any>
+        // 实例
+        actionRef={actionRef}
+        // 标题
+        headerTitle={'短篇小说列表'}
+        // 唯一key
+        rowKey={(r) => r.id}
+        // 按钮
+        toolBarRender={() => {
+          return [
+            <Button
+              type="primary"
+              onClick={() => {
+                setOpen(true);
+              }}
+            >
+              <PlusCircleOutlined />
+              新增书籍
+            </Button>,
+          ];
+        }}
+        //多选
+        rowSelection={{
+          selectedRowKeys: editSelectedRow?.map((item: { id: any }) => item.id),
+          onSelect: (record, selected) => {
+            if (selected) {
+              setEditSelectedRow([...editSelectedRow, record]);
+            } else {
+              setEditSelectedRow(
+                editSelectedRow?.filter((item: { id: any }) => item.id !== record.id),
+              );
+            }
+          },
+          onSelectAll: (selected, rows, changeRows) => {
+            if (selected) {
+              setEditSelectedRow([...editSelectedRow, ...changeRows]);
+            } else {
+              let newArr = editSelectedRow?.filter((item: { id: any }) =>
+                changeRows.every((i) => i?.id !== item?.id),
+              );
+              setEditSelectedRow(newArr);
+            }
+          },
+        }}
+        //多选展示栏
+        tableAlertRender={({ selectedRowKeys, selectedRows }) => {
+          return (
+            <Space size={24}>
+              <span style={{ width: 90, display: 'inline-block' }}>
+                已选 {selectedRowKeys.length} 项
+              </span>
+              <span style={{ color: 'red' }}>
+                {selectedRows
+                  ?.map((item: { bookName: string }) => '《' + item.bookName + '》')
+                  .join()}
+              </span>
+            </Space>
+          );
+        }}
+        // 多选后的按钮操作
+        tableAlertOptionRender={({ selectedRowKeys }) => {
+          return (
+            <Space>
+              <Popconfirm
+                title={null}
+                icon={null}
+                onConfirm={() => {
+                  shelveAll(
+                    editSelectedRow?.map((item) => item.id),
+                    0,
+                  );
+                }}
+                onCancel={() => {
+                  shelveAll(
+                    editSelectedRow?.map((item) => item.id),
+                    1,
+                  );
+                }}
+                okText="全部上架"
+                cancelText="全部下架"
+              >
+                <Button type="primary" disabled={selectedRowKeys.length === 0}>
+                  <ColumnHeightOutlined />
+                  批量上下架
+                </Button>
+              </Popconfirm>
+              <Popconfirm
+                title={<div>确定要删除此批书籍?</div>}
+                onConfirm={() => {
+                  del(editSelectedRow?.map((item) => item.id));
+                }}
+              >
+                <Button type="primary" danger disabled={selectedRowKeys.length === 0}>
+                  <DeleteOutlined />
+                  批量删除
+                </Button>
+              </Popconfirm>
+            </Space>
+          );
+        }}
+        //宽度自适应
+        scroll={{ x: true }}
+        // 加载
+        loading={BookContentLongBookInfoList?.loading}
+        // 搜索的配置
+        search={{
+          labelWidth: 90,
+          searchGutter: [10, 15],
+        }}
+        //处理搜索数据
+        beforeSearchSubmit={(params) => {
+          let newParams = Object.entries(params).reduce((acc: any, [key, value]) => {
+            // 过滤掉空值,包括空字符串和 null
+            if (value !== '' && value != null) {
+              acc[key] = value;
+            }
+            if (key === 'wordCount' && value) {
+              let obj = wordCountRanges[value];
+              acc['startWordCount'] = obj.start;
+              acc['endWordCount'] = obj.end;
+              delete acc[key];
+            }
+            return acc;
+          }, {});
+          return newParams;
+        }}
+        // 数据请求
+        request={async (params) => {
+          return await BookContentLongBookInfoList.run(params);
+        }}
+        // 表
+        columns={columns({
+          authList: state?.authList,
+          labelList: state.labelList,
+          categoryList: state.categoryList,
+          enumList: state?.enumList,
+          lookBook: lookBookList,
+          closeForm,
+          setWorkDirection,
+          del,
+          setEditValues,
+        })}
+      />
+      {/* 新增编辑小说 */}
+      <BetaSchemaForm<DataItem>
+        layout="horizontal"
+        title={!editValues?.id ? '新增书籍' : '编辑书籍'}
+        formRef={formRef}
+        open={open}
+        onOpenChange={(b) => {
+          !b && closeForm(b);
+        }}
+        layoutType={'ModalForm'}
+        rowProps={{
+          gutter: [16, 16],
+        }}
+        colProps={{
+          span: 12,
+        }}
+        grid={true}
+        onValuesChange={(changedValues, allValues) => {
+          // 检查 workDirection 是否变化
+          if ('workDirection' in changedValues) {
+            setWorkDirection(changedValues['workDirection']);
+            // 清空 categoryId 和 labelIds 的值
+            formRef.current?.setFieldsValue({
+              categoryId: undefined,
+              labelIds: undefined,
+            });
+          }
+        }}
+        onFinish={submit}
+        columns={formConfig({
+          authList: state?.authList,
+          enumList: state?.enumList,
+          labelList: state.labelList,
+          categoryList: state.categoryList,
+        })}
+        loading={BookContentLongBookInfoAdd?.loading || BookContentLongBookInfoEdit?.loading}
+      />
+      {/* 阅读小说 */}
+      <Drawer
+        open={!!openBook}
+        placement="right"
+        onClose={() => {
+          setOpneBook(null);
+        }}
+        footer={null}
+        width={'65%'}
+        destroyOnClose={true}
+        // getContainer={false}
+        styles={{ body: { padding: 0 } }}
+        closeIcon={false}
+        title={<div style={{ fontSize: 20 }}>{openBook?.bookName ? openBook?.bookName : ''}</div>}
+      >
+        <ReadBook listData={openBook} next={lookBook} />
+      </Drawer>
+      {/* 段落管理 */}
+      <Item data={editValues} onClose={setEditValues} />
+    </PageContainer>
+  );
+};
+export default Page;

+ 215 - 0
src/pages/book/bookManage/longbookList/item.tsx

@@ -0,0 +1,215 @@
+import UploadFile from '@/components/UploadFile';
+import { useAjax } from '@/Hook/useAjax';
+import {
+  apiLongBookParagraph,
+  apiLongBookParagraphAdd,
+  apiLongBookParagraphDel,
+  apiLongBookParagraphEdit,
+  apiLongBookParagraphList,
+} from '@/services/book/longbookList';
+import { PlusCircleOutlined } from '@ant-design/icons';
+import { ActionType, BetaSchemaForm, ProFormInstance, ProTable } from '@ant-design/pro-components';
+import { Button, Drawer, message } from 'antd';
+import { useRef, useState } from 'react';
+import { childrenColumns } from './tableConfig';
+type DataItem = {
+  name: string;
+  state: string;
+};
+type Props = {
+  data: any;
+  onClose: (v: any) => void;
+};
+const Page: React.FC<Props> = (props) => {
+  let { data, onClose } = props;
+  let [open, setOpen] = useState<any>(null); //新建弹窗
+  const [uploadOpen, setUploadOpen] = useState(false);
+  let [editValues, setEditValues] = useState<any>({});
+  // ======================API=======================
+  let LongBookParagraphList = useAjax((params) => apiLongBookParagraphList(params), {
+    type: 'table',
+  }); //小说章节列表
+  let LongBookParagraphAdd = useAjax((params) => apiLongBookParagraphAdd(params)); //小说章节新增
+  let LongBookParagraphEdit = useAjax((params) => apiLongBookParagraphEdit(params)); //小说章节编辑
+  let LongBookParagraphDel = useAjax((params) => apiLongBookParagraphDel(params)); //小说章节删除
+  let LongBookParagraph = useAjax((id) => apiLongBookParagraph(id)); //查询单个章节
+  const formRef = useRef<ProFormInstance>();
+  const actionRef = useRef<ActionType>();
+
+  // 删除or批量删除
+  const del = (id: any) => {
+    LongBookParagraphDel.run(id).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('删除成功');
+      }
+    });
+  };
+  // 提交表单
+  const submit = async (values: any) => {
+    let api = editValues?.id ? LongBookParagraphEdit : LongBookParagraphAdd;
+    if (editValues?.id) {
+      values.id = editValues?.id;
+    }
+    values.bookId = data.id;
+    api.run(values).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('操作成功!');
+        closeForm(false);
+      }
+    });
+  };
+  // 关闭表单弹窗和重置表单内容
+  const closeForm = (b: boolean, values?: any) => {
+    if (!b) {
+      setEditValues({});
+      formRef?.current?.resetFields?.();
+      setOpen(b);
+    } else {
+      setEditValues(values);
+      LongBookParagraph.run(values.id).then((res: any) => {
+        if (res.code === 200) {
+          formRef?.current?.setFieldsValue(res.data);
+        }
+      });
+
+      setOpen(b);
+    }
+  };
+
+  return (
+    <Drawer
+      open={data?.id}
+      placement="right"
+      onClose={() => {
+        onClose({});
+      }}
+      width={'75%'}
+      footer={null}
+      destroyOnClose={true}
+      closeIcon={false}
+    >
+      <ProTable<any, any>
+        headerTitle={'《' + data?.bookName + '》章节管理'}
+        actionRef={actionRef}
+        rowKey={(r) => r.id}
+        search={false}
+        params={{ bookId: data?.id }}
+        request={async (params) => {
+          if (params.bookId) {
+            return await LongBookParagraphList.run(params);
+          }
+        }}
+        scroll={{ x: 'auto' }}
+        toolBarRender={() => {
+          return [
+            <Button
+              type="primary"
+              onClick={() => {
+                setOpen(true);
+              }}
+            >
+              <PlusCircleOutlined />
+              新增章节
+            </Button>,
+            <Button
+              type="primary"
+              onClick={() => {
+                setUploadOpen(true);
+              }}
+            >
+              <PlusCircleOutlined />
+              上传章节
+            </Button>,
+          ];
+        }}
+        columns={childrenColumns({ del, closeForm })}
+      />
+      {/* 新增段落 */}
+      <BetaSchemaForm<DataItem>
+        layout="horizontal"
+        title={!editValues?.id ? '新增章节' : '编辑章节'}
+        formRef={formRef}
+        open={open}
+        onOpenChange={(b) => {
+          !b && closeForm(b);
+        }}
+        layoutType={'DrawerForm'}
+        rowProps={{
+          gutter: [16, 16],
+        }}
+        labelCol={{ span: 3 }}
+        colProps={{ span: 23 }}
+        grid={true}
+        onFinish={submit}
+        columns={[
+          {
+            title: '章节号',
+            dataIndex: 'chapterNo',
+            valueType: 'digit',
+            formItemProps: {
+              style: { marginBottom: 10 },
+              rules: [
+                {
+                  required: true,
+                  message: '此项为必填项',
+                },
+              ],
+            },
+          },
+          {
+            title: '章节名称',
+            dataIndex: 'chapterName',
+            formItemProps: {
+              style: { marginBottom: 10 },
+              rules: [
+                {
+                  required: true,
+                  message: '此项为必填项',
+                },
+              ],
+            },
+          },
+          {
+            title: '段落内容',
+            dataIndex: 'content',
+            valueType: 'textarea',
+            fieldProps: {
+              rows: 46,
+            },
+            formItemProps: {
+              style: { marginBottom: 10 },
+              rules: [
+                {
+                  required: true,
+                  message: '此项为必填项',
+                },
+              ],
+            },
+          },
+        ]}
+        loading={LongBookParagraphEdit?.loading || LongBookParagraphAdd?.loading}
+      />
+      {/* 上传段落 */}
+      <Drawer
+        title={`上传《${data?.bookName}》段落`}
+        placement="right"
+        width={'39%'}
+        closable={false}
+        onClose={() => {
+          setUploadOpen(false);
+        }}
+        open={uploadOpen}
+      >
+        <UploadFile
+          type="long"
+          bookId={data?.id}
+          refresh={actionRef?.current?.reload}
+          open={uploadOpen}
+        />
+      </Drawer>
+    </Drawer>
+  );
+};
+export default Page;

+ 491 - 0
src/pages/book/bookManage/longbookList/tableConfig.tsx

@@ -0,0 +1,491 @@
+import { ProColumns } from '@ant-design/pro-components';
+import { Badge, Button, Col, Image, Popconfirm, Row, Space, Tag } from 'antd';
+import { createStyles } from 'antd-style';
+const useStyles = createStyles(({ token }) => {
+  return {
+    bookLabel: {
+      color: token.colorTextTertiary,
+    },
+    del: {
+      textDecoration: 'line-through',
+      color: token.colorTextDisabled,
+    },
+    color: {
+      color: token.colorWarningActive,
+    },
+  };
+});
+const brightColors = [
+  '#FF6347', // 番茄红
+  '#FF4500', // 橙红色
+  '#FFD700', // 金黄色
+  '#32CD32', // 鲜绿色
+  '#00FF7F', // 麦绿色
+  '#00CED1', // 暗青色
+  '#4682B4', // 钢蓝色
+  '#6A5ACD', // 鲜紫色
+  '#FF69B4', // 热粉红色
+  '#FF1493', // 深粉红色
+];
+export const columns = (props: {
+  authList?: any[];
+  labelList?: any[];
+  enumList?: { [key: string]: any };
+  categoryList?: any[];
+  lookBook: (data: any) => void;
+  closeForm: (b: boolean, d: any) => void;
+  setEditValues: (v: any) => void;
+  setWorkDirection: (w: any) => void;
+  del: (id: any) => void;
+}): ProColumns<any>[] => {
+  let {
+    authList,
+    labelList,
+    categoryList,
+    enumList,
+    lookBook,
+    closeForm,
+    setWorkDirection,
+    del,
+    setEditValues,
+  } = props;
+  let { styles } = useStyles();
+  return enumList?.BOOK_STATUS
+    ? [
+        {
+          title: '封面',
+          dataIndex: 'picUrl',
+          key: 'picUrl',
+          width: 90,
+          ellipsis: true,
+          align: 'center',
+          hideInSearch: true,
+          render: (a: any, b: any) => {
+            return (
+              <div style={{ position: 'relative' }}>
+                <Image
+                  src={b?.picUrl}
+                  style={{ width: 50 }}
+                  onError={(e: any) => {
+                    e.target.src = localStorage.getItem('nocover');
+                  }}
+                />
+              </div>
+            );
+          },
+        },
+        {
+          title: '作品详情',
+          dataIndex: 'authorId',
+          key: 'authorId',
+          ellipsis: true,
+          hideInSearch: true,
+          width: 500,
+          render: (a, b) => {
+            let { bookName, wordCount, labels, bookStatus, categoryName, authorName, score } = b;
+            let arr = enumList?.BOOK_STATUS?.values;
+            return (
+              <Row>
+                <Col span={24}>
+                  <Space size={[5, 0]} wrap>
+                    <a
+                      style={{ fontSize: 14, color: '#337ab7', cursor: 'pointer' }}
+                      onClick={() => {
+                        lookBook?.({ ...b, pageNum: 1, pageSize: 2 });
+                      }}
+                    >
+                      [{categoryName}]{bookName}
+                    </a>
+                    <span style={{ fontSize: 11 }} className={styles.bookLabel}>
+                      [{arr[bookStatus]?.description}]
+                    </span>
+                    <Space size={[0, 0]}>
+                      {labels?.map((tags: { id: string; name: string }, index: number) => {
+                        return (
+                          <Tag key={tags?.id} color={brightColors[index]}>
+                            {tags?.name}
+                          </Tag>
+                        );
+                      })}
+                    </Space>
+                  </Space>
+                </Col>
+                <Col span={24} className={styles.bookLabel}>
+                  <span>
+                    <span>作者</span>:
+                  </span>
+                  {authorName}
+                </Col>
+                <Col span={24} className={styles.bookLabel}>
+                  <span>总字数:</span>
+                  {wordCount || 0}
+                </Col>
+                <Col span={24} className={styles.bookLabel}>
+                  <span>评分:</span>
+                  {score || 0}
+                </Col>
+              </Row>
+            );
+          },
+        },
+        {
+          title: '所属频道',
+          dataIndex: 'workDirection',
+          key: 'workDirection',
+          align: 'center',
+          width: 80,
+          ellipsis: true,
+          hideInSearch: true,
+          render: (a: any, b: any) => {
+            return enumList?.WORK_DIRECTION?.values[b.workDirection]?.description;
+          },
+        },
+        {
+          title: '点击量',
+          dataIndex: 'visitCount',
+          key: 'visitCount',
+          width: 90,
+          ellipsis: true,
+          align: 'center',
+          hideInSearch: true,
+          render: (a, b) => {
+            let { visitCount } = b;
+            return visitCount;
+          },
+        },
+        {
+          title: '上架状态',
+          dataIndex: 'shelveStatus',
+          key: 'shelveStatus',
+          width: 90,
+          ellipsis: true,
+          align: 'center',
+          hideInSearch: true,
+          render: (a: any, b) => {
+            let arr: any = enumList?.SHELVE_STATUS?.values;
+            let { shelveStatus } = b;
+            return (
+              arr[shelveStatus]?.description && (
+                <Badge
+                  text={arr[shelveStatus]?.description}
+                  status={shelveStatus == 0 ? 'processing' : 'default'}
+                />
+              )
+            );
+          },
+        },
+        {
+          title: '来源',
+          dataIndex: 'source',
+          key: 'source',
+          width: 90,
+          ellipsis: true,
+          align: 'center',
+          hideInSearch: true,
+          render: (a: any, b) => {
+            let { source } = b;
+            let arr: any = new Map(
+              enumList?.SOURCE?.values?.map(({ value, description }: any) => [value, description]),
+            );
+            return arr.get(source);
+          },
+        },
+        {
+          title: '操作',
+          dataIndex: 'cz',
+          key: 'cz',
+          width: 90,
+          ellipsis: true,
+          align: 'center',
+          hideInSearch: true,
+          render: (a: any, b: any) => {
+            return (
+              <Space size={[0, 0]}>
+                <Button
+                  type="link"
+                  size="small"
+                  className={styles.color}
+                  onClick={() => {
+                    setEditValues(b);
+                  }}
+                >
+                  章节管理
+                </Button>
+                <Button
+                  type="link"
+                  size="small"
+                  onClick={() => {
+                    closeForm(true, b);
+                  }}
+                >
+                  编辑
+                </Button>
+                <Popconfirm
+                  title={
+                    <div>
+                      确定要删除<span style={{ color: 'red' }}>{b.bookName}</span>此书籍?
+                    </div>
+                  }
+                  onConfirm={() => {
+                    del(b.id);
+                  }}
+                >
+                  <Button type="link" size="small" danger>
+                    删除
+                  </Button>
+                </Popconfirm>
+              </Space>
+            );
+          },
+        },
+        // 搜索条件
+        {
+          title: '小说名称',
+          dataIndex: 'bookName',
+          valueType: 'text',
+          hideInTable: true,
+          fieldProps: { placeholder: '请输入小说名称' },
+          colSize: 1,
+        },
+        {
+          title: '作者',
+          dataIndex: 'authorId',
+          valueType: 'select',
+          hideInTable: true,
+          fieldProps: { showSearch: true, placeholder: '请选择作者' },
+          colSize: 1,
+          valueEnum: new Map(authList?.map(({ id, authorName }) => [id, authorName])),
+        },
+        {
+          title: '频道',
+          dataIndex: 'workDirection',
+          valueType: 'segmented',
+          hideInTable: true,
+          fieldProps: {
+            type: 'primary',
+            style: { width: 'auto' },
+            onChange: (value) => {
+              setWorkDirection(value);
+            },
+          },
+          colSize: 3,
+          initialValue: '',
+          valueEnum: () => {
+            let arr = enumList?.WORK_DIRECTION?.values;
+            return arr
+              ? new Map(
+                  [{ value: '', description: '全部' }, ...arr]?.map(
+                    ({ value, description }: any) => [value, description],
+                  ),
+                )
+              : {};
+          },
+        },
+        {
+          title: '分类',
+          dataIndex: 'categoryId',
+          valueType: 'segmented',
+          hideInTable: true,
+          hideInSearch: !labelList,
+          fieldProps: { type: 'primary', style: { width: 'auto' } },
+          colSize: 3,
+          initialValue: '',
+          valueEnum: () => {
+            let arr = categoryList || [];
+            return new Map([{ id: '', name: '全部' }, ...arr]?.map((item) => [item.id, item.name]));
+          },
+        },
+        {
+          title: '标签',
+          dataIndex: 'labelIds',
+          valueType: 'segmented',
+          hideInTable: true,
+          hideInSearch: !labelList,
+          fieldProps: { type: 'primary', style: { width: 'auto' } },
+          colSize: 3,
+          initialValue: '',
+          valueEnum: () => {
+            let arr = labelList || [];
+            return new Map([{ id: '', name: '全部' }, ...arr]?.map((item) => [item.id, item.name]));
+          },
+        },
+        {
+          title: '来源',
+          dataIndex: 'source',
+          valueType: 'segmented',
+          hideInTable: true,
+          fieldProps: { type: 'primary', style: { width: 'auto' } },
+          colSize: 3,
+          initialValue: '',
+          valueEnum: () => {
+            let arr = enumList?.SOURCE?.values;
+            return arr
+              ? new Map(
+                  [{ value: '', description: '全部' }, ...arr]?.map(
+                    ({ value, description }: any) => [value, description],
+                  ),
+                )
+              : {};
+          },
+        },
+        {
+          title: '连载',
+          dataIndex: 'bookStatus',
+          valueType: 'segmented',
+          hideInTable: true,
+          fieldProps: { type: 'primary', style: { width: 'auto' } },
+          colSize: 3,
+          initialValue: '',
+          valueEnum: () => {
+            let arr = enumList?.BOOK_STATUS?.values;
+            return arr
+              ? new Map(
+                  [{ value: '', description: '全部' }, ...arr]?.map(
+                    ({ value, description }: any) => [value, description],
+                  ),
+                )
+              : {};
+          },
+        },
+        {
+          title: '上架',
+          dataIndex: 'shelveStatus',
+          valueType: 'segmented',
+          hideInTable: true,
+          fieldProps: { type: 'primary', style: { width: 'auto' } },
+          colSize: 3,
+          initialValue: '',
+          valueEnum: () => {
+            let arr = enumList?.SHELVE_STATUS?.values;
+            return arr
+              ? new Map(
+                  [{ value: '', description: '全部' }, ...arr]?.map(
+                    ({ value, description }: any) => [value, description],
+                  ),
+                )
+              : {};
+          },
+        },
+        {
+          title: '字数',
+          dataIndex: 'wordCount',
+          valueType: 'segmented',
+          hideInTable: true,
+          fieldProps: { type: 'primary', style: { width: 'auto' }, block: true },
+          colSize: 3,
+          initialValue: '',
+          valueEnum: {
+            '': '全部',
+            '0-2': '2万内',
+            '2-5': '2-5万',
+            '5-10': '5-10万',
+            '10-20': '10-20万',
+            '20-40': '20-40万',
+            '40-100': '40-100万',
+            '100-150': '100-150万',
+            '150-200': '150-200万',
+            '200-300': '200-300万',
+            '300-0': '300万以上',
+          },
+        },
+      ]
+    : [];
+};
+
+export const childrenColumns = (props: {
+  del: (id: any) => void;
+  closeForm: (b: boolean, v: any) => void;
+}): ProColumns<any>[] => {
+  let { del, closeForm } = props;
+  return [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      key: 'id',
+      align: 'center',
+      width: 50,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '章节号',
+      dataIndex: 'chapterNo',
+      key: 'chapterNo',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '章节名称',
+      dataIndex: 'chapterName',
+      key: 'chapterName',
+      align: 'center',
+      width: 120,
+      ellipsis: true,
+    },
+    {
+      title: '章节字数',
+      dataIndex: 'wordCount',
+      key: 'wordCount',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'createTime',
+      key: 'createTime',
+      align: 'center',
+      width: 150,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '更新时间',
+      dataIndex: 'updateTime',
+      key: 'updateTime',
+      align: 'center',
+      width: 150,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '操作',
+      dataIndex: 'cz',
+      key: 'cz',
+      width: 90,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      render: (a, b) => {
+        return (
+          <Space>
+            <Button
+              type="link"
+              size="small"
+              onClick={() => {
+                closeForm(true, b);
+              }}
+            >
+              编辑
+            </Button>
+            <Popconfirm
+              title={
+                <div>
+                  确定要删除<span style={{ color: 'red' }}>{b.paragraphNo}</span>号段落?
+                </div>
+              }
+              onConfirm={() => {
+                del(b.id);
+              }}
+            >
+              <a style={{ color: 'red' }}>删除</a>
+            </Popconfirm>
+          </Space>
+        );
+      },
+    },
+  ];
+};

+ 280 - 0
src/pages/book/bookManage/shortbookList/formConfig.tsx

@@ -0,0 +1,280 @@
+import UploadImg from '@/components/uploadImg';
+import { ProFormColumnsType } from '@ant-design/pro-components';
+
+function formConfig(props: {
+  authList?: any[];
+  enumList?: { [key: string]: any };
+  labelList?: any[];
+  categoryList?: any[];
+}): ProFormColumnsType<{
+  name: string;
+  state: string;
+}>[] {
+  let { enumList, labelList, categoryList, authList } = props;
+  return [
+    {
+      valueType: 'group',
+      colProps: {
+        span: 12,
+      },
+      rowProps: { gutter: [14, 14] },
+      columns: [
+        {
+          title: '频道',
+          dataIndex: 'workDirection',
+          valueType: 'select',
+          fieldProps: { showSearch: true, placeholder: '请选择频道' },
+          colProps: {
+            span: 24,
+          },
+          formItemProps: {
+            style: { marginBottom: 10 },
+            rules: [
+              {
+                required: true,
+                message: '此项为必填项',
+              },
+            ],
+          },
+          valueEnum: () => {
+            let arr = enumList?.WORK_DIRECTION?.values;
+            return new Map(arr?.map(({ value, description }: any) => [value, description]));
+          },
+        },
+        {
+          valueType: 'dependency',
+          name: ['workDirection'],
+          columns: ({ workDirection }) => {
+            console.log('workDirection', workDirection);
+            return [
+              {
+                title: '分类',
+                dataIndex: 'categoryId',
+                valueType: 'select',
+                fieldProps: {
+                  showSearch: true,
+                  placeholder: '请选择分类',
+                  disabled: workDirection != 0 && workDirection != 1,
+                },
+                colProps: {
+                  span: 24,
+                },
+                formItemProps: {
+                  style: { marginBottom: 10 },
+                  rules: [
+                    {
+                      required: true,
+                      message: '此项为必填项',
+                    },
+                  ],
+                },
+                valueEnum: () => {
+                  let arr = categoryList || [];
+                  return new Map([...arr]?.map((item) => [item.id, item.name]));
+                },
+              },
+              {
+                title: '标签',
+                dataIndex: 'labelIds',
+                valueType: 'select',
+                colProps: {
+                  span: 24,
+                },
+                formItemProps: {
+                  style: { marginBottom: 10 },
+                  rules: [
+                    {
+                      required: true,
+                      message: '此项为必填项',
+                    },
+                  ],
+                },
+                convertValue: (value) => {
+                  let arr =
+                    typeof value === 'string' ? value?.split(',')?.map((s) => Number(s)) : value;
+                  return arr;
+                },
+                transform: (value) => {
+                  return typeof value != 'string' ? value.join() : value;
+                },
+                fieldProps: {
+                  mode: 'multiple',
+                  showSearch: true,
+                  placeholder: '请选择标签',
+                  disabled: workDirection != 0 && workDirection != 1,
+                },
+                valueEnum: () => {
+                  let arr = labelList || [];
+                  return new Map([...arr]?.map((item) => [item.id, item.name]));
+                },
+              },
+            ];
+          },
+        },
+        {
+          title: '书名',
+          dataIndex: 'bookName',
+          colProps: {
+            span: 24,
+          },
+          fieldProps: { placeholder: '请输入小说名称' },
+          formItemProps: {
+            style: { marginBottom: 10 },
+            rules: [
+              {
+                required: true,
+                message: '此项为必填项',
+              },
+            ],
+          },
+        },
+        {
+          title: '作者',
+          dataIndex: 'authorId',
+          valueType: 'select',
+          fieldProps: { showSearch: true, placeholder: '请选择作者' },
+          colProps: {
+            span: 24,
+          },
+          formItemProps: {
+            style: { marginBottom: 10 },
+            rules: [
+              {
+                required: true,
+                message: '此项为必填项',
+              },
+            ],
+          },
+          valueEnum: new Map(authList?.map(({ id, authorName }) => [id, authorName])),
+        },
+        {
+          title: '来源',
+          dataIndex: 'source',
+          valueType: 'select',
+          fieldProps: { showSearch: true, placeholder: '请选择来源' },
+          colProps: {
+            span: 24,
+          },
+          formItemProps: {
+            style: { marginBottom: 10 },
+            rules: [
+              {
+                required: true,
+                message: '此项为必填项',
+              },
+            ],
+          },
+          valueEnum: () => {
+            let arr = enumList?.SOURCE?.values;
+            return arr
+              ? new Map([...arr]?.map(({ value, description }: any) => [value, description]))
+              : {};
+          },
+        },
+        {
+          title: '描述',
+          dataIndex: 'bookDesc',
+          valueType: 'textarea',
+          colProps: {
+            span: 24,
+          },
+          fieldProps: { placeholder: '请输入小说描述' },
+          formItemProps: {
+            style: { marginBottom: 10 },
+            rules: [
+              {
+                required: true,
+                message: '此项为必填项',
+              },
+            ],
+          },
+        },
+      ],
+    },
+    {
+      valueType: 'group',
+      colProps: {
+        span: 12,
+      },
+      rowProps: { gutter: [14, 14] },
+      columns: [
+        {
+          title: '书籍状态',
+          dataIndex: 'bookStatus',
+          valueType: 'select',
+          colProps: {
+            span: 24,
+          },
+          formItemProps: {
+            style: { marginBottom: 10 },
+            rules: [
+              {
+                required: true,
+                message: '此项为必填项',
+              },
+            ],
+          },
+          valueEnum: () => {
+            let arr = enumList?.BOOK_STATUS?.values;
+            return new Map(arr?.map(({ value, description }: any) => [value, description]));
+          },
+        },
+        {
+          title: '上架状态',
+          dataIndex: 'shelveStatus',
+          valueType: 'select',
+          colProps: {
+            span: 24,
+          },
+          formItemProps: {
+            style: { marginBottom: 10 },
+            rules: [
+              {
+                required: true,
+                message: '此项为必填项',
+              },
+            ],
+          },
+          valueEnum: () => {
+            let arr = enumList?.SHELVE_STATUS?.values;
+            return new Map(arr?.map(({ value, description }: any) => [value, description]));
+          },
+        },
+        {
+          title: '封面',
+          dataIndex: 'picUrl',
+          colProps: {
+            span: 24,
+          },
+          formItemProps: {
+            style: { marginBottom: 10 },
+            rules: [
+              {
+                required: true,
+                message: '此项为必填项',
+              },
+            ],
+          },
+          renderFormItem: () => {
+            return (
+              <UploadImg
+                size={{
+                  width: 300,
+                  height: 400,
+                  evalW: '!=',
+                  evalH: '!=',
+                  msg: '需要图片大小300*400',
+                }}
+                isEdit={true}
+                type="image"
+                isCropper
+              />
+            );
+          },
+        },
+      ],
+    },
+  ];
+}
+
+export default formConfig;

+ 329 - 4
src/pages/book/bookManage/shortbookList/index.tsx

@@ -1,4 +1,329 @@
-function Page(){
-    return "短篇小说"
-}
-export default Page
+import ReadText from '@/components/readText';
+import { useAjax } from '@/Hook/useAjax';
+import {
+  apiBookContentShortBookInfoAdd,
+  apiBookContentShortBookInfoBatch,
+  apiBookContentShortBookInfoDel,
+  apiBookContentShortBookInfoDels,
+  apiBookContentShortBookInfoEdit,
+  apiBookContentShortBookInfoList,
+  apiShortBookGetInfo,
+} from '@/services/book/shortbookList';
+import { ColumnHeightOutlined, DeleteOutlined, PlusCircleOutlined } from '@ant-design/icons';
+import {
+  ActionType,
+  BetaSchemaForm,
+  PageContainer,
+  ProFormInstance,
+  ProTable,
+} from '@ant-design/pro-components';
+import { useModel } from '@umijs/max';
+import { Button, Drawer, message, Popconfirm, Space } from 'antd';
+import { useEffect, useRef, useState } from 'react';
+import formConfig from './formConfig';
+import Item from './item';
+import { columns } from './tableConfig';
+const wordCountRanges: any = {
+  '': { start: null, end: null }, // 全部
+  '0-2': { start: 0, end: 2 * 10000 },
+  '2-5': { start: 2 * 10000, end: 5 * 10000 },
+  '5-10': { start: 5 * 10000, end: 10 * 10000 },
+  '10-20': { start: 10 * 10000, end: 20 * 10000 },
+  '20-40': { start: 20 * 10000, end: 40 * 10000 },
+  '40-100': { start: 40 * 10000, end: 100 * 10000 },
+  '100-150': { start: 100 * 10000, end: 150 * 10000 },
+  '150-200': { start: 150 * 10000, end: 200 * 10000 },
+  '200-300': { start: 200 * 10000, end: 300 * 10000 },
+  '300-0': { start: 300 * 10000, end: null }, // 300万以上
+};
+type DataItem = {
+  name: string;
+  state: string;
+};
+const Page: React.FC = () => {
+  let { state, getLabelAndClassList } = useModel('global');
+  let [open, setOpen] = useState<any>(null); //付费配置
+  let [editValues, setEditValues] = useState<any>({});
+  let [workDirection, setWorkDirection] = useState<any>(null);
+  const [openBook, setOpneBook] = useState<any>(null); //阅读小说
+  const [editSelectedRow, setEditSelectedRow] = useState<any[]>([]); //小说列表选择
+
+  // ======================API=======================
+  let BookContentShortBookInfoList = useAjax((params) => apiBookContentShortBookInfoList(params), {
+    type: 'table',
+  }); //获取书籍列表
+  let BookContentShortBookInfoAdd = useAjax((params) => apiBookContentShortBookInfoAdd(params)); //新增书籍
+  let BookContentShortBookInfoEdit = useAjax((params) => apiBookContentShortBookInfoEdit(params)); //编辑书籍
+  let BookContentShortBookInfoDel = useAjax((id) => apiBookContentShortBookInfoDel(id)); //单个删除书籍
+  let BookContentShortBookInfoDels = useAjax((ids) => apiBookContentShortBookInfoDels(ids)); //批量删除书籍
+  let BookContentShortBookInfoBatch = useAjax((params) => apiBookContentShortBookInfoBatch(params)); //批量上下架
+  let ShortBookGetInfo = useAjax((params) => apiShortBookGetInfo(params)); //查询单个段落
+  const formRef = useRef<ProFormInstance>();
+  const actionRef = useRef<ActionType>();
+
+  // 获取标签和分类
+  useEffect(() => {
+    getLabelAndClassList({ workDirection });
+  }, [workDirection]);
+  // 看小说
+  const lookBook = (params: any) => {
+    let { id, pageNum, pageSize } = params;
+    return ShortBookGetInfo.run({ id, pageNum, pageSize }).then((res) => {
+      setOpneBook({ ...res.data, ...params });
+      return res.data;
+    });
+  };
+  // 删除or批量删除
+  const del = (ids: any) => {
+    let api = Array.isArray(ids) ? BookContentShortBookInfoDels : BookContentShortBookInfoDel;
+    api.run(ids).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('删除成功');
+        setEditSelectedRow([]);
+      }
+    });
+  };
+  // 批量上下架
+  const shelveAll = (ids: any[], shelve: 0 | 1) => {
+    BookContentShortBookInfoBatch.run({ ids, shelve }).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('批量' + (shelve === 0 ? '上架成功' : '下架成功'));
+      }
+    });
+  };
+  // 提交表单
+  const submit = async (values: any) => {
+    let api = editValues?.id ? BookContentShortBookInfoEdit : BookContentShortBookInfoAdd;
+    if (editValues?.id) {
+      values.id = editValues?.id;
+    }
+    api.run(values).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('操作成功!');
+        closeForm(false);
+      }
+    });
+  };
+  // 关闭表单弹窗和重置表单内容
+  const closeForm = (b: boolean, values?: any) => {
+    if (!b) {
+      setEditValues({});
+      formRef?.current?.resetFields?.();
+      setOpen(b);
+    } else {
+      setEditValues(values);
+      setWorkDirection(values.workDirection);
+      setTimeout(() => {
+        formRef?.current?.setFieldsValue(values);
+      }, 100);
+      setOpen(b);
+    }
+  };
+  return (
+    <PageContainer title={false} tabProps={{ type: 'card' }}>
+      <ProTable<any, any>
+        // 实例
+        actionRef={actionRef}
+        // 标题
+        headerTitle={'短篇小说列表'}
+        // 唯一key
+        rowKey={(r) => r.id}
+        // 按钮
+        toolBarRender={() => {
+          return [
+            <Button
+              type="primary"
+              onClick={() => {
+                setOpen(true);
+              }}
+            >
+              <PlusCircleOutlined />
+              新增书籍
+            </Button>,
+          ];
+        }}
+        //多选
+        rowSelection={{
+          selectedRowKeys: editSelectedRow?.map((item: { id: any }) => item.id),
+          onSelect: (record, selected) => {
+            if (selected) {
+              setEditSelectedRow([...editSelectedRow, record]);
+            } else {
+              setEditSelectedRow(
+                editSelectedRow?.filter((item: { id: any }) => item.id !== record.id),
+              );
+            }
+          },
+          onSelectAll: (selected, rows, changeRows) => {
+            if (selected) {
+              setEditSelectedRow([...editSelectedRow, ...changeRows]);
+            } else {
+              let newArr = editSelectedRow?.filter((item: { id: any }) =>
+                changeRows.every((i) => i?.id !== item?.id),
+              );
+              setEditSelectedRow(newArr);
+            }
+          },
+        }}
+        //多选展示栏
+        tableAlertRender={({ selectedRowKeys, selectedRows }) => {
+          return (
+            <Space size={24}>
+              <span style={{ width: 90, display: 'inline-block' }}>
+                已选 {selectedRowKeys.length} 项
+              </span>
+              <span style={{ color: 'red' }}>
+                {selectedRows
+                  ?.map((item: { bookName: string }) => '《' + item.bookName + '》')
+                  .join()}
+              </span>
+            </Space>
+          );
+        }}
+        // 多选后的按钮操作
+        tableAlertOptionRender={({ selectedRowKeys }) => {
+          return (
+            <Space>
+              <Popconfirm
+                title={null}
+                icon={null}
+                onConfirm={() => {
+                  shelveAll(
+                    editSelectedRow?.map((item) => item.id),
+                    0,
+                  );
+                }}
+                onCancel={() => {
+                  shelveAll(
+                    editSelectedRow?.map((item) => item.id),
+                    1,
+                  );
+                }}
+                okText="全部上架"
+                cancelText="全部下架"
+              >
+                <Button type="primary" disabled={selectedRowKeys.length === 0}>
+                  <ColumnHeightOutlined />
+                  批量上下架
+                </Button>
+              </Popconfirm>
+              <Popconfirm
+                title={<div>确定要删除此批书籍?</div>}
+                onConfirm={() => {
+                  del(editSelectedRow?.map((item) => item.id));
+                }}
+              >
+                <Button type="primary" danger disabled={selectedRowKeys.length === 0}>
+                  <DeleteOutlined />
+                  批量删除
+                </Button>
+              </Popconfirm>
+            </Space>
+          );
+        }}
+        //宽度自适应
+        scroll={{ x: true }}
+        // 加载
+        loading={BookContentShortBookInfoList?.loading}
+        // 搜索的配置
+        search={{
+          labelWidth: 90,
+          searchGutter: [10, 15],
+        }}
+        //处理搜索数据
+        beforeSearchSubmit={(params) => {
+          let newParams = Object.entries(params).reduce((acc: any, [key, value]) => {
+            // 过滤掉空值,包括空字符串和 null
+            if (value !== '' && value != null) {
+              acc[key] = value;
+            }
+            if (key === 'wordCount' && value) {
+              let obj = wordCountRanges[value];
+              acc['startWordCount'] = obj.start;
+              acc['endWordCount'] = obj.end;
+              delete acc[key];
+            }
+            return acc;
+          }, {});
+          return newParams;
+        }}
+        // 数据请求
+        request={async (params) => {
+          return await BookContentShortBookInfoList.run(params);
+        }}
+        // 表
+        columns={columns({
+          authList: state?.authList,
+          labelList: state.labelList,
+          categoryList: state.categoryList,
+          enumList: state?.enumList,
+          lookBook,
+          closeForm,
+          setWorkDirection,
+          del,
+          setEditValues,
+        })}
+      />
+      {/* 新增编辑小说 */}
+      <BetaSchemaForm<DataItem>
+        layout="horizontal"
+        title={!editValues?.id ? '新增书籍' : '编辑书籍'}
+        formRef={formRef}
+        open={open}
+        onOpenChange={(b) => {
+          !b && closeForm(b);
+        }}
+        layoutType={'ModalForm'}
+        rowProps={{
+          gutter: [16, 16],
+        }}
+        colProps={{
+          span: 12,
+        }}
+        grid={true}
+        onValuesChange={(changedValues, allValues) => {
+          // 检查 workDirection 是否变化
+          if ('workDirection' in changedValues) {
+            setWorkDirection(changedValues['workDirection']);
+            // 清空 categoryId 和 labelIds 的值
+            formRef.current?.setFieldsValue({
+              categoryId: undefined,
+              labelIds: undefined,
+            });
+          }
+        }}
+        onFinish={submit}
+        columns={formConfig({
+          authList: state?.authList,
+          enumList: state?.enumList,
+          labelList: state.labelList,
+          categoryList: state.categoryList,
+        })}
+        loading={BookContentShortBookInfoAdd?.loading || BookContentShortBookInfoEdit?.loading}
+      />
+      {/* 阅读小说 */}
+      <Drawer
+        open={!!openBook}
+        placement="right"
+        onClose={() => {
+          setOpneBook(null);
+        }}
+        footer={null}
+        width={'65%'}
+        destroyOnClose={true}
+        // getContainer={false}
+        styles={{ body: { padding: 0 } }}
+        closeIcon={false}
+        title={<div style={{ fontSize: 20 }}>{openBook?.bookName ? openBook?.bookName : ''}</div>}
+      >
+        <ReadText data={openBook} next={lookBook} />
+      </Drawer>
+      {/* 段落管理 */}
+      <Item data={editValues} onClose={setEditValues} />
+    </PageContainer>
+  );
+};
+export default Page;

+ 202 - 0
src/pages/book/bookManage/shortbookList/item.tsx

@@ -0,0 +1,202 @@
+import UploadFile from '@/components/UploadFile';
+import { useAjax } from '@/Hook/useAjax';
+import {
+  apiShortBookParagraph,
+  apiShortBookParagraphAdd,
+  apiShortBookParagraphDel,
+  apiShortBookParagraphEdit,
+  apiShortBookParagraphList,
+} from '@/services/book/shortbookList';
+import { PlusCircleOutlined } from '@ant-design/icons';
+import { ActionType, BetaSchemaForm, ProFormInstance, ProTable } from '@ant-design/pro-components';
+import { Button, Drawer, message } from 'antd';
+import { useRef, useState } from 'react';
+import { childrenColumns } from './tableConfig';
+type DataItem = {
+  name: string;
+  state: string;
+};
+type Props = {
+  data: any;
+  onClose: (v: any) => void;
+};
+const Page: React.FC<Props> = (props) => {
+  let { data, onClose } = props;
+  let [open, setOpen] = useState<any>(null); //新建弹窗
+  const [uploadOpen, setUploadOpen] = useState(false);
+  let [editValues, setEditValues] = useState<any>({});
+  // ======================API=======================
+  let ShortBookParagraphList = useAjax((params) => apiShortBookParagraphList(params), {
+    type: 'table',
+  }); //小说段落列表
+  let ShortBookParagraphAdd = useAjax((params) => apiShortBookParagraphAdd(params)); //小说段落新增
+  let ShortBookParagraphEdit = useAjax((params) => apiShortBookParagraphEdit(params)); //小说段落编辑
+  let ShortBookParagraphDel = useAjax((params) => apiShortBookParagraphDel(params)); //小说段落删除
+  let ShortBookParagraph = useAjax((id) => apiShortBookParagraph(id)); //查询单个段落
+  const formRef = useRef<ProFormInstance>();
+  const actionRef = useRef<ActionType>();
+
+  // 删除or批量删除
+  const del = (id: any) => {
+    ShortBookParagraphDel.run(id).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('删除成功');
+      }
+    });
+  };
+  // 提交表单
+  const submit = async (values: any) => {
+    let api = editValues?.id ? ShortBookParagraphEdit : ShortBookParagraphAdd;
+    if (editValues?.id) {
+      values.id = editValues?.id;
+    }
+    values.bookId = data.id;
+    api.run(values).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('操作成功!');
+        closeForm(false);
+      }
+    });
+  };
+  // 关闭表单弹窗和重置表单内容
+  const closeForm = (b: boolean, values?: any) => {
+    if (!b) {
+      setEditValues({});
+      formRef?.current?.resetFields?.();
+      setOpen(b);
+    } else {
+      setEditValues(values);
+      ShortBookParagraph.run(values.id).then((res: any) => {
+        if (res.code === 200) {
+          formRef?.current?.setFieldsValue(res.data);
+        }
+      });
+
+      setOpen(b);
+    }
+  };
+
+  return (
+    <Drawer
+      open={data?.id}
+      placement="right"
+      onClose={() => {
+        onClose({});
+      }}
+      width={'75%'}
+      footer={null}
+      destroyOnClose={true}
+      closeIcon={false}
+    >
+      <ProTable<any, any>
+        headerTitle={'《' + data?.bookName + '》段落管理'}
+        actionRef={actionRef}
+        rowKey={(r) => r.id}
+        search={false}
+        params={{ bookId: data?.id }}
+        scroll={{ x: 'auto' }}
+        request={async (params) => {
+          if (params.bookId) {
+            return await ShortBookParagraphList.run(params);
+          }
+        }}
+        toolBarRender={() => {
+          return [
+            <Button
+              type="primary"
+              onClick={() => {
+                setOpen(true);
+              }}
+            >
+              <PlusCircleOutlined />
+              新增段落
+            </Button>,
+            <Button
+              type="primary"
+              onClick={() => {
+                setUploadOpen(true);
+              }}
+            >
+              <PlusCircleOutlined />
+              上传段落
+            </Button>,
+          ];
+        }}
+        columns={childrenColumns({ del, closeForm })}
+      />
+      {/* 新增段落 */}
+      <BetaSchemaForm<DataItem>
+        layout="horizontal"
+        title={!editValues?.id ? '新增段落' : '编辑段落'}
+        formRef={formRef}
+        open={open}
+        onOpenChange={(b) => {
+          !b && closeForm(b);
+        }}
+        layoutType={'DrawerForm'}
+        rowProps={{
+          gutter: [16, 16],
+        }}
+        labelCol={{ span: 3 }}
+        colProps={{ span: 23 }}
+        grid={true}
+        onFinish={submit}
+        columns={[
+          {
+            title: '段落号',
+            dataIndex: 'paragraphNo',
+            valueType: 'digit',
+            formItemProps: {
+              style: { marginBottom: 10 },
+              rules: [
+                {
+                  required: true,
+                  message: '此项为必填项',
+                },
+              ],
+            },
+          },
+          {
+            title: '段落内容',
+            dataIndex: 'content',
+            valueType: 'textarea',
+            fieldProps: {
+              rows: 46,
+            },
+            formItemProps: {
+              style: { marginBottom: 10 },
+              rules: [
+                {
+                  required: true,
+                  message: '此项为必填项',
+                },
+              ],
+            },
+          },
+        ]}
+        loading={ShortBookParagraphAdd?.loading || ShortBookParagraphEdit?.loading}
+      />
+      {/* 上传段落 */}
+      <Drawer
+        title={`上传《${data?.bookName}》段落`}
+        placement="right"
+        width={'39%'}
+        closable={false}
+        onClose={() => {
+          setUploadOpen(false);
+        }}
+        open={uploadOpen}
+      >
+        <UploadFile
+          type="short"
+          bookId={data?.id}
+          refresh={actionRef?.current?.reload}
+          open={uploadOpen}
+        />
+      </Drawer>
+    </Drawer>
+  );
+};
+export default Page;

+ 483 - 0
src/pages/book/bookManage/shortbookList/tableConfig.tsx

@@ -0,0 +1,483 @@
+import { ProColumns } from '@ant-design/pro-components';
+import { Badge, Button, Col, Image, Popconfirm, Row, Space, Tag } from 'antd';
+import { createStyles } from 'antd-style';
+const useStyles = createStyles(({ token }) => {
+  return {
+    bookLabel: {
+      color: token.colorTextTertiary,
+    },
+    del: {
+      textDecoration: 'line-through',
+      color: token.colorTextDisabled,
+    },
+    color: {
+      color: token.colorWarningActive,
+    },
+  };
+});
+const brightColors = [
+  '#FF6347', // 番茄红
+  '#FF4500', // 橙红色
+  '#FFD700', // 金黄色
+  '#32CD32', // 鲜绿色
+  '#00FF7F', // 麦绿色
+  '#00CED1', // 暗青色
+  '#4682B4', // 钢蓝色
+  '#6A5ACD', // 鲜紫色
+  '#FF69B4', // 热粉红色
+  '#FF1493', // 深粉红色
+];
+export const columns = (props: {
+  authList?: any[];
+  labelList?: any[];
+  enumList?: { [key: string]: any };
+  categoryList?: any[];
+  lookBook: (data: any) => void;
+  closeForm: (b: boolean, d: any) => void;
+  setEditValues: (v: any) => void;
+  setWorkDirection: (w: any) => void;
+  del: (id: any) => void;
+}): ProColumns<any>[] => {
+  let {
+    authList,
+    labelList,
+    categoryList,
+    enumList,
+    lookBook,
+    closeForm,
+    setWorkDirection,
+    del,
+    setEditValues,
+  } = props;
+  let { styles } = useStyles();
+  return enumList?.BOOK_STATUS
+    ? [
+        {
+          title: '封面',
+          dataIndex: 'picUrl',
+          key: 'picUrl',
+          width: 90,
+          ellipsis: true,
+          align: 'center',
+          hideInSearch: true,
+          render: (a: any, b: any) => {
+            return (
+              <div style={{ position: 'relative' }}>
+                <Image
+                  src={b?.picUrl}
+                  style={{ width: 50 }}
+                  onError={(e: any) => {
+                    e.target.src = localStorage.getItem('nocover');
+                  }}
+                />
+              </div>
+            );
+          },
+        },
+        {
+          title: '作品详情',
+          dataIndex: 'authorId',
+          key: 'authorId',
+          ellipsis: true,
+          hideInSearch: true,
+          width: 500,
+          render: (a, b) => {
+            let { bookName, wordCount, labels, bookStatus, categoryName, authorName, score } = b;
+            let arr = enumList?.BOOK_STATUS?.values;
+            return (
+              <Row>
+                <Col span={24}>
+                  <Space size={[5, 0]} wrap>
+                    <a
+                      style={{ fontSize: 14, color: '#337ab7', cursor: 'pointer' }}
+                      onClick={() => {
+                        lookBook?.({ ...b, pageNum: 1, pageSize: 2 });
+                      }}
+                    >
+                      [{categoryName}]{bookName}
+                    </a>
+                    <span style={{ fontSize: 11 }} className={styles.bookLabel}>
+                      [{arr[bookStatus]?.description}]
+                    </span>
+                    <Space size={[0, 0]}>
+                      {labels?.map((tags: { id: string; name: string }, index: number) => {
+                        return (
+                          <Tag key={tags?.id} color={brightColors[index]}>
+                            {tags?.name}
+                          </Tag>
+                        );
+                      })}
+                    </Space>
+                  </Space>
+                </Col>
+                <Col span={24} className={styles.bookLabel}>
+                  <span>
+                    <span>作者</span>:
+                  </span>
+                  {authorName}
+                </Col>
+                <Col span={24} className={styles.bookLabel}>
+                  <span>总字数:</span>
+                  {wordCount || 0}
+                </Col>
+                <Col span={24} className={styles.bookLabel}>
+                  <span>评分:</span>
+                  {score || 0}
+                </Col>
+              </Row>
+            );
+          },
+        },
+        {
+          title: '所属频道',
+          dataIndex: 'workDirection',
+          key: 'workDirection',
+          align: 'center',
+          width: 80,
+          ellipsis: true,
+          hideInSearch: true,
+          render: (a: any, b: any) => {
+            return enumList?.WORK_DIRECTION?.values[b.workDirection]?.description;
+          },
+        },
+        {
+          title: '点击量',
+          dataIndex: 'visitCount',
+          key: 'visitCount',
+          width: 90,
+          ellipsis: true,
+          align: 'center',
+          hideInSearch: true,
+          render: (a, b) => {
+            let { visitCount } = b;
+            return visitCount;
+          },
+        },
+        {
+          title: '上架状态',
+          dataIndex: 'shelveStatus',
+          key: 'shelveStatus',
+          width: 90,
+          ellipsis: true,
+          align: 'center',
+          hideInSearch: true,
+          render: (a: any, b) => {
+            let arr: any = enumList?.SHELVE_STATUS?.values;
+            let { shelveStatus } = b;
+            return (
+              arr[shelveStatus]?.description && (
+                <Badge
+                  text={arr[shelveStatus]?.description}
+                  status={shelveStatus == 0 ? 'processing' : 'default'}
+                />
+              )
+            );
+          },
+        },
+        {
+          title: '来源',
+          dataIndex: 'source',
+          key: 'source',
+          width: 90,
+          ellipsis: true,
+          align: 'center',
+          hideInSearch: true,
+          render: (a: any, b) => {
+            let { source } = b;
+            let arr: any = new Map(
+              enumList?.SOURCE?.values?.map(({ value, description }: any) => [value, description]),
+            );
+            return arr.get(source);
+          },
+        },
+        {
+          title: '操作',
+          dataIndex: 'cz',
+          key: 'cz',
+          width: 90,
+          ellipsis: true,
+          align: 'center',
+          hideInSearch: true,
+          render: (a: any, b: any) => {
+            return (
+              <Space size={[0, 0]}>
+                <Button
+                  type="link"
+                  size="small"
+                  className={styles.color}
+                  onClick={() => {
+                    setEditValues(b);
+                  }}
+                >
+                  段落管理
+                </Button>
+                <Button
+                  type="link"
+                  size="small"
+                  onClick={() => {
+                    closeForm(true, b);
+                  }}
+                >
+                  编辑
+                </Button>
+                <Popconfirm
+                  title={
+                    <div>
+                      确定要删除<span style={{ color: 'red' }}>{b.bookName}</span>此书籍?
+                    </div>
+                  }
+                  onConfirm={() => {
+                    del(b.id);
+                  }}
+                >
+                  <Button type="link" size="small" danger>
+                    删除
+                  </Button>
+                </Popconfirm>
+              </Space>
+            );
+          },
+        },
+        // 搜索条件
+        {
+          title: '小说名称',
+          dataIndex: 'bookName',
+          valueType: 'text',
+          hideInTable: true,
+          fieldProps: { placeholder: '请输入小说名称' },
+          colSize: 1,
+        },
+        {
+          title: '作者',
+          dataIndex: 'authorId',
+          valueType: 'select',
+          hideInTable: true,
+          fieldProps: { showSearch: true, placeholder: '请选择作者' },
+          colSize: 1,
+          valueEnum: new Map(authList?.map(({ id, authorName }) => [id, authorName])),
+        },
+        {
+          title: '频道',
+          dataIndex: 'workDirection',
+          valueType: 'segmented',
+          hideInTable: true,
+          fieldProps: {
+            type: 'primary',
+            style: { width: 'auto' },
+            onChange: (value) => {
+              setWorkDirection(value);
+            },
+          },
+          colSize: 3,
+          initialValue: '',
+          valueEnum: () => {
+            let arr = enumList?.WORK_DIRECTION?.values;
+            return arr
+              ? new Map(
+                  [{ value: '', description: '全部' }, ...arr]?.map(
+                    ({ value, description }: any) => [value, description],
+                  ),
+                )
+              : {};
+          },
+        },
+        {
+          title: '分类',
+          dataIndex: 'categoryId',
+          valueType: 'segmented',
+          hideInTable: true,
+          hideInSearch: !labelList,
+          fieldProps: { type: 'primary', style: { width: 'auto' } },
+          colSize: 3,
+          initialValue: '',
+          valueEnum: () => {
+            let arr = categoryList || [];
+            return new Map([{ id: '', name: '全部' }, ...arr]?.map((item) => [item.id, item.name]));
+          },
+        },
+        {
+          title: '标签',
+          dataIndex: 'labelIds',
+          valueType: 'segmented',
+          hideInTable: true,
+          hideInSearch: !labelList,
+          fieldProps: { type: 'primary', style: { width: 'auto' } },
+          colSize: 3,
+          initialValue: '',
+          valueEnum: () => {
+            let arr = labelList || [];
+            return new Map([{ id: '', name: '全部' }, ...arr]?.map((item) => [item.id, item.name]));
+          },
+        },
+        {
+          title: '来源',
+          dataIndex: 'source',
+          valueType: 'segmented',
+          hideInTable: true,
+          fieldProps: { type: 'primary', style: { width: 'auto' } },
+          colSize: 3,
+          initialValue: '',
+          valueEnum: () => {
+            let arr = enumList?.SOURCE?.values;
+            return arr
+              ? new Map(
+                  [{ value: '', description: '全部' }, ...arr]?.map(
+                    ({ value, description }: any) => [value, description],
+                  ),
+                )
+              : {};
+          },
+        },
+        {
+          title: '连载',
+          dataIndex: 'bookStatus',
+          valueType: 'segmented',
+          hideInTable: true,
+          fieldProps: { type: 'primary', style: { width: 'auto' } },
+          colSize: 3,
+          initialValue: '',
+          valueEnum: () => {
+            let arr = enumList?.BOOK_STATUS?.values;
+            return arr
+              ? new Map(
+                  [{ value: '', description: '全部' }, ...arr]?.map(
+                    ({ value, description }: any) => [value, description],
+                  ),
+                )
+              : {};
+          },
+        },
+        {
+          title: '上架',
+          dataIndex: 'shelveStatus',
+          valueType: 'segmented',
+          hideInTable: true,
+          fieldProps: { type: 'primary', style: { width: 'auto' } },
+          colSize: 3,
+          initialValue: '',
+          valueEnum: () => {
+            let arr = enumList?.SHELVE_STATUS?.values;
+            return arr
+              ? new Map(
+                  [{ value: '', description: '全部' }, ...arr]?.map(
+                    ({ value, description }: any) => [value, description],
+                  ),
+                )
+              : {};
+          },
+        },
+        {
+          title: '字数',
+          dataIndex: 'wordCount',
+          valueType: 'segmented',
+          hideInTable: true,
+          fieldProps: { type: 'primary', style: { width: 'auto' }, block: true },
+          colSize: 3,
+          initialValue: '',
+          valueEnum: {
+            '': '全部',
+            '0-2': '2万内',
+            '2-5': '2-5万',
+            '5-10': '5-10万',
+            '10-20': '10-20万',
+            '20-40': '20-40万',
+            '40-100': '40-100万',
+            '100-150': '100-150万',
+            '150-200': '150-200万',
+            '200-300': '200-300万',
+            '300-0': '300万以上',
+          },
+        },
+      ]
+    : [];
+};
+
+export const childrenColumns = (props: {
+  del: (id: any) => void;
+  closeForm: (b: boolean, v: any) => void;
+}): ProColumns<any>[] => {
+  let { del, closeForm } = props;
+  return [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      key: 'id',
+      align: 'center',
+      width: 50,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '段落号',
+      dataIndex: 'paragraphNo',
+      key: 'paragraphNo',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '段落字数',
+      dataIndex: 'wordCount',
+      key: 'wordCount',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'createTime',
+      key: 'createTime',
+      align: 'center',
+      width: 150,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '更新时间',
+      dataIndex: 'updateTime',
+      key: 'updateTime',
+      align: 'center',
+      width: 150,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '操作',
+      dataIndex: 'cz',
+      key: 'cz',
+      width: 90,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      render: (a, b) => {
+        return (
+          <Space>
+            <Button
+              type="link"
+              size="small"
+              onClick={() => {
+                closeForm(true, b);
+              }}
+            >
+              编辑
+            </Button>
+            <Popconfirm
+              title={
+                <div>
+                  确定要删除<span style={{ color: 'red' }}>{b.paragraphNo}</span>号段落?
+                </div>
+              }
+              onConfirm={() => {
+                del(b.id);
+              }}
+            >
+              <a style={{ color: 'red' }}>删除</a>
+            </Popconfirm>
+          </Space>
+        );
+      },
+    },
+  ];
+};

+ 303 - 0
src/pages/distribution/book/longBook/formConfig.tsx

@@ -0,0 +1,303 @@
+import { ProFormColumnsType } from '@ant-design/pro-components';
+
+function formConfig(props: {
+  enumList?: { [key: string]: any };
+  bookList?: any[];
+  distributorInfoList?: any[];
+}): ProFormColumnsType<{
+  name: string;
+  state: string;
+}>[] {
+  let { enumList, bookList, distributorInfoList } = props;
+  return [
+    {
+      title: 'VIP免费',
+      dataIndex: 'vipFree',
+      valueType: 'radio',
+      valueEnum: () => {
+        let arr = enumList?.VIP_FREE?.values;
+        return arr
+          ? new Map([...arr]?.map(({ value, description }: any) => [value, description]))
+          : {};
+      },
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      title: '付费类型',
+      dataIndex: 'paymentType',
+      valueType: 'radio',
+      valueEnum: () => {
+        let arr = enumList?.PAYMENT_TYPE?.values;
+        return arr
+          ? new Map([...arr]?.map(({ value, description }: any) => [value, description]))
+          : {};
+      },
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      valueType: 'dependency',
+      name: ['paymentType'],
+      columns: ({ paymentType }) => {
+        return [
+          {
+            title: '收费类型',
+            dataIndex: 'paymentOption',
+            valueType: 'radio',
+            hideInForm: paymentType != 2,
+            valueEnum: () => {
+              let arr = enumList?.PAYMENT_OPTION?.values;
+              return arr
+                ? new Map([...arr]?.map(({ value, description }: any) => [value, description]))
+                : {};
+            },
+            formItemProps: {
+              style: { marginBottom: 10 },
+              rules: [
+                {
+                  required: true,
+                  message: '此项为必填项',
+                },
+              ],
+            },
+          },
+          {
+            title: '收费货币',
+            dataIndex: 'paymentCategory',
+            valueType: 'radio',
+            hideInForm: paymentType == 0,
+            valueEnum: () => {
+              let arr = enumList?.PAYMENT_CATEGORY?.values;
+              return arr
+                ? new Map([...arr]?.map(({ value, description }: any) => [value, description]))
+                : {};
+            },
+            formItemProps: {
+              style: { marginBottom: 10 },
+              rules: [
+                {
+                  required: true,
+                  message: '此项为必填项',
+                },
+              ],
+            },
+          },
+          {
+            title: '开始付费章节',
+            dataIndex: 'beginPayChapterNo',
+            valueType: 'select',
+            fieldProps: { showSearch: true, placeholder: '开始付费章节' },
+            hideInForm: paymentType != 2,
+            valueEnum: () => {
+              let arr = bookList;
+              return arr
+                ? new Map(
+                    [...arr]?.map(({ chapterInfo: { chapterNo, chapterName } }: any) => [
+                      chapterNo,
+                      chapterName,
+                    ]),
+                  )
+                : {};
+            },
+            formItemProps: {
+              style: { marginBottom: 10 },
+              rules: [
+                {
+                  required: true,
+                  message: '此项为必填项',
+                },
+              ],
+            },
+          },
+          {
+            valueType: 'dependency',
+            name: ['paymentCategory', 'paymentType'],
+            columns: ({ paymentCategory, paymentType }) => {
+              return [
+                {
+                  title: '收费书币',
+                  dataIndex: 'paymentCoin',
+                  valueType: 'digit',
+                  hideInForm: paymentType === 0 || paymentCategory == 0,
+                  formItemProps: {
+                    style: { marginBottom: 10 },
+                    min: 0,
+                    rules: [
+                      {
+                        required: true,
+                        message: '此项为必填项',
+                      },
+                    ],
+                  },
+                  fieldProps: {
+                    min: 0,
+                    addonAfter: '书币',
+                    precision: 0,
+                  },
+                },
+                {
+                  title: '收费金额',
+                  dataIndex: 'paymentAmount',
+                  valueType: 'money',
+                  hideInForm: paymentType === 0 || paymentCategory == 1,
+                  formItemProps: {
+                    style: { marginBottom: 10 },
+                    rules: [
+                      {
+                        required: true,
+                        message: '此项为必填项',
+                      },
+                    ],
+                  },
+                  fieldProps: {
+                    min: 0,
+                    addonAfter: '元',
+                  },
+                },
+              ];
+            },
+          },
+        ];
+      },
+    },
+    {
+      title: '是否开放给所有分销商',
+      dataIndex: 'publicDistributor',
+      valueType: 'radio',
+      valueEnum: () => {
+        let arr = enumList?.PUBLIC_DISTRIBUTOR?.values;
+        return arr
+          ? new Map([...arr]?.map(({ value, description }: any) => [value, description]))
+          : {};
+      },
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      title: '是否允许分销商配置价格',
+      dataIndex: 'allowDistributorConfig',
+      valueType: 'radio',
+      valueEnum: () => {
+        let arr = enumList?.ALLOW_DISTRIBUTOR_CONFIG?.values;
+        return arr
+          ? new Map([...arr]?.map(({ value, description }: any) => [value, description]))
+          : {};
+      },
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      dataIndex: 'bookDistributorConfig',
+      valueType: 'formList',
+      title: '小说分销商配置',
+      fieldProps: {
+        creatorButtonProps: {
+          creatorButtonText: '新增小说分销商配置',
+        },
+      },
+      formItemProps: {
+        style: { marginBottom: 10 },
+      },
+      columns: [
+        {
+          valueType: 'group',
+          fieldProps: {
+            style: {
+              display: 'flex',
+              alignItems: 'center',
+              flexFlow: 'row',
+            },
+          },
+          columns: [
+            {
+              dataIndex: 'distributorId',
+              fieldProps: { showSearch: true, placeholder: '请选择分销商' },
+              colProps: { span: 10 },
+              formItemProps: {
+                style: { marginBottom: 10, minWidth: 150 },
+                rules: [
+                  {
+                    required: true,
+                    message: '此项为必填项',
+                  },
+                ],
+              },
+              valueEnum: () => {
+                let arr = distributorInfoList;
+                return arr
+                  ? new Map([...arr]?.map(({ id, companyName }: any) => [id, companyName]))
+                  : {};
+              },
+            },
+            {
+              valueType: 'dependency',
+              name: ['configRelations'],
+              columns({ configRelations }) {
+                console.log('configRelations', configRelations);
+                return [
+                  {
+                    valueType: 'checkbox',
+                    dataIndex: 'configRelations',
+                    colProps: { span: 14 },
+                    formItemProps: {
+                      style: { width: 300 },
+                      rules: [
+                        {
+                          required: true,
+                          message: '此项为必填项',
+                        },
+                      ],
+                    },
+                    valueEnum: (a) => {
+                      let arr = enumList?.CONFIG_RELATIONS?.values;
+                      let obj: any = {};
+                      for (let item of arr) {
+                        obj[item.value] = {
+                          text: item.description,
+                          disabled:
+                            configRelations?.length > 0 && !configRelations.includes(item.value),
+                        };
+                      }
+                      return obj;
+                    },
+                  },
+                ];
+              },
+            },
+          ],
+        },
+      ],
+    },
+  ];
+}
+
+export default formConfig;

+ 132 - 4
src/pages/distribution/book/longBook/index.tsx

@@ -1,4 +1,132 @@
-function Page(){
-    return "长篇小说"
-}
-export default Page
+import { useAjax } from '@/Hook/useAjax';
+import { apiDistributorInfoList } from '@/services/distribution/info';
+import {
+  apiLongBookInfoBookConfig,
+  apiLongBookInfoChapterAllList,
+  apiLongBookInfoConfigDetail,
+  apiLongBookInfoList,
+} from '@/services/distribution/longBook';
+import {
+  ActionType,
+  BetaSchemaForm,
+  PageContainer,
+  ProFormInstance,
+  ProTable,
+} from '@ant-design/pro-components';
+import { useModel } from '@umijs/max';
+import { message } from 'antd';
+import { useEffect, useRef, useState } from 'react';
+import formConfig from './formConfig';
+import { columns } from './tableConfig';
+
+type DataItem = {
+  name: string;
+  state: string;
+};
+const Page: React.FC = () => {
+  let { state, getLabelAndClassList } = useModel('global');
+  const formRef = useRef<ProFormInstance>();
+  const actionRef = useRef<ActionType>();
+  let [workDirection, setWorkDirection] = useState<any>(null);
+  let [open, setOpen] = useState<any>(null); //新增作者
+  let [editValues, setEditValues] = useState<any>({});
+  // ======================API=======================
+  let DistributorInfoList = useAjax((params) => apiDistributorInfoList(params)); //分销商列表
+  let BookInfoList = useAjax((params) => apiLongBookInfoList(params), { type: 'table' }); //获取书籍列表
+  let BookInfoBookConfig = useAjax((params) => apiLongBookInfoBookConfig(params)); //小说信息配置
+  let BookInfoChapterAllList = useAjax((id) => apiLongBookInfoChapterAllList(id)); //全部列表无分页
+  let BookInfoConfigDetail = useAjax((id) => apiLongBookInfoConfigDetail(id)); //小说配置详情
+  // 获取标签和分类
+  useEffect(() => {
+    getLabelAndClassList({ workDirection });
+  }, [workDirection]);
+  // 编辑
+  const edit = (item: any) => {
+    DistributorInfoList.run();
+    BookInfoChapterAllList.run(item.bookId).then((res) => {
+      setOpen(true);
+      BookInfoConfigDetail.run(item.bookId).then((dres) => {
+        console.log(dres.data, item);
+        setEditValues(dres.data);
+        formRef?.current?.setFieldsValue({ ...dres.data });
+      });
+    });
+  };
+  //编辑
+  const submit = async (values: any) => {
+    if (editValues?.id) {
+      values.id = editValues?.id;
+    }
+    if (values.bookDistributorConfig) {
+      values.bookDistributorConfig = values.bookDistributorConfig?.map((item: any) => {
+        return { ...item, bookId: editValues.bookId };
+      });
+    }
+    BookInfoBookConfig.run(values).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('操作成功!');
+        closeForm(false);
+      }
+    });
+    console.log(values);
+  };
+  // 关闭表单弹窗和重置表单内容
+  const closeForm = (b: boolean, values?: any) => {
+    if (!b) {
+      setEditValues({});
+      formRef?.current?.resetFields?.();
+      setOpen(b);
+    } else {
+    }
+  };
+  return (
+    <PageContainer>
+      <ProTable<any, any>
+        headerTitle={'长篇小说配置信息列表'}
+        actionRef={actionRef}
+        rowKey={(r) => r.bookId}
+        // 搜索的配置
+        search={{
+          labelWidth: 90,
+          searchGutter: [10, 15],
+        }}
+        request={async (params) => {
+          return await BookInfoList.run(params);
+        }}
+        scroll={{ x: 'auto' }}
+        columns={columns({
+          authList: state?.authList,
+          labelList: state.labelList,
+          categoryList: state.categoryList,
+          enumList: state?.enumList,
+          setWorkDirection,
+          edit,
+        })}
+        // bordered
+      />
+      <BetaSchemaForm<DataItem>
+        title={!editValues?.id ? '新增作者' : '编辑作者'}
+        formRef={formRef}
+        width={900}
+        open={open}
+        onOpenChange={(b) => {
+          !b && closeForm(b);
+        }}
+        layoutType={'ModalForm'}
+        labelCol={{ span: 8 }}
+        wrapperCol={{ span: 14 }}
+        grid={true}
+        layout="horizontal"
+        onFinish={submit}
+        columns={formConfig({
+          enumList: state?.enumList,
+          bookList: BookInfoChapterAllList?.data?.data,
+          distributorInfoList: DistributorInfoList?.data?.data,
+        })}
+        loading={BookInfoBookConfig?.loading}
+      />
+    </PageContainer>
+  );
+};
+export default Page;

+ 518 - 0
src/pages/distribution/book/longBook/tableConfig.tsx

@@ -0,0 +1,518 @@
+import { ProColumns } from '@ant-design/pro-components';
+import { Badge, Button, Col, Image, Row, Space, Tag } from 'antd';
+import { createStyles } from 'antd-style';
+const useStyles = createStyles(({ token }) => {
+  return {
+    bookLabel: {
+      color: token.colorTextTertiary,
+    },
+    del: {
+      textDecoration: 'line-through',
+      color: token.colorTextDisabled,
+    },
+    color: {
+      color: token.colorWarningActive,
+    },
+  };
+});
+const brightColors = [
+  '#FF6347', // 番茄红
+  '#FF4500', // 橙红色
+  '#FFD700', // 金黄色
+  '#32CD32', // 鲜绿色
+  '#00FF7F', // 麦绿色
+  '#00CED1', // 暗青色
+  '#4682B4', // 钢蓝色
+  '#6A5ACD', // 鲜紫色
+  '#FF69B4', // 热粉红色
+  '#FF1493', // 深粉红色
+];
+export const columns = (props: {
+  authList?: any[];
+  labelList?: any[];
+  enumList?: { [key: string]: any };
+  categoryList?: any[];
+  setWorkDirection: (w: any) => void;
+  edit: (v: any) => void;
+}): ProColumns<any>[] => {
+  let { authList, labelList, categoryList, enumList, setWorkDirection, edit } = props;
+  let { styles } = useStyles();
+  return [
+    {
+      title: '封面',
+      dataIndex: 'picUrl',
+      key: 'picUrl',
+      width: 90,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      render: (a: any, b: any) => {
+        return (
+          <div style={{ position: 'relative' }}>
+            <Image
+              src={b?.longBookInfo?.picUrl}
+              style={{ width: 50 }}
+              onError={(e: any) => {
+                e.target.src = localStorage.getItem('nocover');
+              }}
+            />
+          </div>
+        );
+      },
+    },
+    {
+      title: '作品详情',
+      dataIndex: 'authorId',
+      key: 'authorId',
+      ellipsis: true,
+      hideInSearch: true,
+      width: 500,
+      render: (a, b) => {
+        let { bookName, wordCount, labelInfoList, bookStatus, categoryInfo, authorInfo, score } =
+          b?.longBookInfo;
+        let arr = enumList?.BOOK_STATUS?.values;
+        return (
+          <Row>
+            <Col span={24}>
+              <Space size={[5, 0]} wrap>
+                <a
+                  style={{ fontSize: 14, color: '#337ab7', cursor: 'pointer' }}
+                  onClick={() => {
+                    // lookBook?.({ ...b, pageNum: 1, pageSize: 2 })
+                  }}
+                >
+                  [{categoryInfo?.name}]{bookName}
+                </a>
+                <span style={{ fontSize: 11 }} className={styles.bookLabel}>
+                  [{arr[bookStatus]?.description}]
+                </span>
+                <Space size={[0, 0]}>
+                  {labelInfoList?.map((tags: { id: string; name: string }, index: number) => {
+                    return (
+                      <Tag key={tags?.id} color={brightColors[index]}>
+                        {tags?.name}
+                      </Tag>
+                    );
+                  })}
+                </Space>
+              </Space>
+            </Col>
+            <Col span={24} className={styles.bookLabel}>
+              <span>
+                <span>作者</span>:
+              </span>
+              {authorInfo?.authorName}
+            </Col>
+            <Col span={24} className={styles.bookLabel}>
+              <span>总字数:</span>
+              {wordCount || 0}
+            </Col>
+            <Col span={24} className={styles.bookLabel}>
+              <span>评分:</span>
+              {score || 0}
+            </Col>
+          </Row>
+        );
+      },
+    },
+    {
+      title: '所属频道',
+      dataIndex: 'workDirection',
+      key: 'workDirection',
+      align: 'center',
+      width: 80,
+      ellipsis: true,
+      hideInSearch: true,
+      render: (a: any, b: any) => {
+        return enumList?.WORK_DIRECTION?.values[b.longBookInfo?.workDirection]?.description;
+      },
+    },
+    {
+      title: '点击量',
+      dataIndex: 'visitCount',
+      key: 'visitCount',
+      width: 90,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      render: (a, b) => {
+        let { visitCount } = b?.longBookInfo;
+        return visitCount;
+      },
+    },
+    {
+      title: '上架状态',
+      dataIndex: 'shelveStatus',
+      key: 'shelveStatus',
+      width: 90,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      render: (a: any, b) => {
+        let arr: any = enumList?.SHELVE_STATUS?.values;
+        let { shelveStatus } = b?.longBookInfo;
+        return (
+          arr[shelveStatus]?.description && (
+            <Badge
+              text={arr[shelveStatus]?.description}
+              status={shelveStatus == 0 ? 'processing' : 'default'}
+            />
+          )
+        );
+      },
+    },
+    {
+      title: '来源',
+      dataIndex: 'source',
+      key: 'source',
+      width: 90,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      render: (a: any, b) => {
+        let { source } = b?.longBookInfo;
+        let arr: any = new Map(
+          enumList?.SOURCE?.values?.map(({ value, description }: any) => [value, description]),
+        );
+        return arr.get(source);
+      },
+    },
+    {
+      title: 'configId',
+      dataIndex: 'configId',
+      key: 'configId',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+      render: (a) => {
+        return a;
+      },
+    },
+    {
+      title: '是否开放给所有分销商',
+      dataIndex: 'publicDistributor',
+      key: 'publicDistributor',
+      align: 'center',
+      width: 150,
+      ellipsis: true,
+      hideInSearch: true,
+      render: (a, b) => {
+        let isHot = a ? '是' : '否';
+        return <Badge status={a ? 'processing' : 'default'} text={isHot} />;
+      },
+    },
+    {
+      title: '是否允许分销商配置价格',
+      dataIndex: 'allowDistributorConfig',
+      key: 'allowDistributorConfig',
+      align: 'center',
+      width: 150,
+      ellipsis: true,
+      hideInSearch: true,
+      render: (a, b) => {
+        let isHot = a ? '是' : '否';
+        return <Badge status={a ? 'processing' : 'default'} text={isHot} />;
+      },
+    },
+    {
+      title: '付费类型',
+      dataIndex: 'paymentType',
+      key: 'paymentType',
+      width: 150,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      valueEnum: () => {
+        let arr = enumList?.PAYMENT_TYPE?.values;
+        return arr
+          ? new Map([...arr]?.map(({ value, description }: any) => [value, description]))
+          : {};
+      },
+    },
+    {
+      title: '收费类型',
+      dataIndex: 'paymentOption',
+      key: 'paymentOption',
+      width: 100,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      valueEnum: () => {
+        let arr = enumList?.PAYMENT_OPTION?.values;
+        return arr
+          ? new Map([...arr]?.map(({ value, description }: any) => [value, description]))
+          : {};
+      },
+    },
+    {
+      title: 'vip限免',
+      dataIndex: 'vipFree',
+      key: 'vipFree',
+      width: 150,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      render: (a, b) => {
+        return <Badge text={a ? '是' : '否'} status={a ? 'processing' : 'default'} />;
+      },
+    },
+    {
+      title: '收费金额',
+      dataIndex: 'paymentAmount',
+      key: 'paymentAmount',
+      width: 150,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      render: (a, b) => {
+        return (
+          <span
+            style={
+              b?.paymentCategory === 1 ? { textDecoration: 'line-through', color: '#bab8b8' } : {}
+            }
+          >
+            {b?.paymentAmount || '-'}
+          </span>
+        );
+      },
+    },
+    {
+      title: '收费书币',
+      dataIndex: 'paymentCoin',
+      key: 'paymentCoin',
+      width: 150,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      render: (a, b) => {
+        return (
+          <span
+            style={
+              b?.paymentCategory === 0 ? { textDecoration: 'line-through', color: '#bab8b8' } : {}
+            }
+          >
+            {b?.paymentCoin || '-'}
+          </span>
+        );
+      },
+    },
+    {
+      title: '操作',
+      dataIndex: 'cz',
+      key: 'cz',
+      width: 160,
+      align: 'center',
+      className: 'table_fixed',
+      hideInSearch: true,
+      render: (a: any, b: any) => {
+        return (
+          <Space>
+            <Button
+              onClick={() => {
+                edit(b);
+              }}
+              type="link"
+            >
+              信息配置
+            </Button>
+          </Space>
+        );
+      },
+    },
+    // 搜索条件
+    {
+      title: '小说名称',
+      dataIndex: 'bookName',
+      valueType: 'text',
+      hideInTable: true,
+      fieldProps: { placeholder: '请输入小说名称' },
+      colSize: 1,
+    },
+    {
+      title: '作者',
+      dataIndex: 'authorId',
+      valueType: 'select',
+      hideInTable: true,
+      fieldProps: { showSearch: true, placeholder: '请选择作者' },
+      colSize: 1,
+      valueEnum: new Map(authList?.map(({ id, authorName }) => [id, authorName])),
+    },
+    {
+      title: '频道',
+      dataIndex: 'workDirection',
+      valueType: 'segmented',
+      hideInTable: true,
+      fieldProps: {
+        type: 'primary',
+        style: { width: 'auto' },
+        onChange: (value) => {
+          setWorkDirection(value);
+        },
+      },
+      colSize: 3,
+      initialValue: '',
+      valueEnum: () => {
+        let arr = enumList?.WORK_DIRECTION?.values;
+        return arr
+          ? new Map(
+              [{ value: '', description: '全部' }, ...arr]?.map(({ value, description }: any) => [
+                value,
+                description,
+              ]),
+            )
+          : {};
+      },
+    },
+    {
+      title: '分类',
+      dataIndex: 'categoryId',
+      valueType: 'segmented',
+      hideInTable: true,
+      hideInSearch: !labelList,
+      fieldProps: { type: 'primary', style: { width: 'auto' } },
+      colSize: 3,
+      initialValue: '',
+      valueEnum: () => {
+        let arr = categoryList || [];
+        return new Map([{ id: '', name: '全部' }, ...arr]?.map((item) => [item.id, item.name]));
+      },
+    },
+    {
+      title: '标签',
+      dataIndex: 'labelIds',
+      valueType: 'segmented',
+      hideInTable: true,
+      hideInSearch: !labelList,
+      fieldProps: { type: 'primary', style: { width: 'auto' } },
+      colSize: 3,
+      initialValue: '',
+      valueEnum: () => {
+        let arr = labelList || [];
+        return new Map([{ id: '', name: '全部' }, ...arr]?.map((item) => [item.id, item.name]));
+      },
+    },
+    {
+      title: '来源',
+      dataIndex: 'source',
+      valueType: 'segmented',
+      hideInTable: true,
+      fieldProps: { type: 'primary', style: { width: 'auto' } },
+      colSize: 3,
+      initialValue: '',
+      valueEnum: () => {
+        let arr = enumList?.SOURCE?.values;
+        return arr
+          ? new Map(
+              [{ value: '', description: '全部' }, ...arr]?.map(({ value, description }: any) => [
+                value,
+                description,
+              ]),
+            )
+          : {};
+      },
+    },
+    {
+      title: '连载',
+      dataIndex: 'bookStatus',
+      valueType: 'segmented',
+      hideInTable: true,
+      fieldProps: { type: 'primary', style: { width: 'auto' } },
+      colSize: 3,
+      initialValue: '',
+      valueEnum: () => {
+        let arr = enumList?.BOOK_STATUS?.values;
+        return arr
+          ? new Map(
+              [{ value: '', description: '全部' }, ...arr]?.map(({ value, description }: any) => [
+                value,
+                description,
+              ]),
+            )
+          : {};
+      },
+    },
+    {
+      title: '上架',
+      dataIndex: 'shelveStatus',
+      valueType: 'segmented',
+      hideInTable: true,
+      fieldProps: { type: 'primary', style: { width: 'auto' } },
+      colSize: 3,
+      initialValue: '',
+      valueEnum: () => {
+        let arr = enumList?.SHELVE_STATUS?.values;
+        return arr
+          ? new Map(
+              [{ value: '', description: '全部' }, ...arr]?.map(({ value, description }: any) => [
+                value,
+                description,
+              ]),
+            )
+          : {};
+      },
+    },
+    {
+      title: '付费',
+      dataIndex: 'paymentType',
+      valueType: 'segmented',
+      hideInTable: true,
+      fieldProps: { type: 'primary', style: { width: 'auto' } },
+      colSize: 3,
+      initialValue: '',
+      valueEnum: () => {
+        let arr = enumList?.PAYMENT_TYPE?.values;
+        return arr
+          ? new Map(
+              [{ value: '', description: '全部' }, ...arr]?.map(({ value, description }: any) => [
+                value,
+                description,
+              ]),
+            )
+          : {};
+      },
+    },
+    {
+      title: 'VIP',
+      dataIndex: 'vipFree',
+      valueType: 'segmented',
+      hideInTable: true,
+      fieldProps: { type: 'primary', style: { width: 'auto' } },
+      colSize: 3,
+      initialValue: '',
+      valueEnum: () => {
+        let arr = enumList?.VIP_FREE?.values;
+        return arr
+          ? new Map(
+              [{ value: '', description: '全部' }, ...arr]?.map(({ value, description }: any) => [
+                value,
+                description,
+              ]),
+            )
+          : {};
+      },
+    },
+    {
+      title: '字数',
+      dataIndex: 'wordCount',
+      valueType: 'segmented',
+      hideInTable: true,
+      fieldProps: { type: 'primary', style: { width: 'auto' }, block: true },
+      colSize: 3,
+      initialValue: '',
+      valueEnum: {
+        '': '全部',
+        '0-2': '2万内',
+        '2-5': '2-5万',
+        '5-10': '5-10万',
+        '10-20': '10-20万',
+        '20-40': '20-40万',
+        '40-100': '40-100万',
+        '100-150': '100-150万',
+        '150-200': '150-200万',
+        '200-300': '200-300万',
+        '300-0': '300万以上',
+      },
+    },
+  ];
+};

+ 303 - 0
src/pages/distribution/book/shortBook/formConfig.tsx

@@ -0,0 +1,303 @@
+import { ProFormColumnsType } from '@ant-design/pro-components';
+
+function formConfig(props: {
+  enumList?: { [key: string]: any };
+  bookList?: any[];
+  distributorInfoList?: any[];
+}): ProFormColumnsType<{
+  name: string;
+  state: string;
+}>[] {
+  let { enumList, bookList, distributorInfoList } = props;
+  return [
+    {
+      title: 'VIP免费',
+      dataIndex: 'vipFree',
+      valueType: 'radio',
+      valueEnum: () => {
+        let arr = enumList?.VIP_FREE?.values;
+        return arr
+          ? new Map([...arr]?.map(({ value, description }: any) => [value, description]))
+          : {};
+      },
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      title: '付费类型',
+      dataIndex: 'paymentType',
+      valueType: 'radio',
+      valueEnum: () => {
+        let arr = enumList?.PAYMENT_TYPE?.values;
+        return arr
+          ? new Map([...arr]?.map(({ value, description }: any) => [value, description]))
+          : {};
+      },
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      valueType: 'dependency',
+      name: ['paymentType'],
+      columns: ({ paymentType }) => {
+        return [
+          {
+            title: '收费类型',
+            dataIndex: 'paymentOption',
+            valueType: 'radio',
+            hideInForm: paymentType != 2,
+            valueEnum: () => {
+              let arr = enumList?.PAYMENT_OPTION?.values;
+              return arr
+                ? new Map([...arr]?.map(({ value, description }: any) => [value, description]))
+                : {};
+            },
+            formItemProps: {
+              style: { marginBottom: 10 },
+              rules: [
+                {
+                  required: true,
+                  message: '此项为必填项',
+                },
+              ],
+            },
+          },
+          {
+            title: '收费货币',
+            dataIndex: 'paymentCategory',
+            valueType: 'radio',
+            hideInForm: paymentType == 0,
+            valueEnum: () => {
+              let arr = enumList?.PAYMENT_CATEGORY?.values;
+              return arr
+                ? new Map([...arr]?.map(({ value, description }: any) => [value, description]))
+                : {};
+            },
+            formItemProps: {
+              style: { marginBottom: 10 },
+              rules: [
+                {
+                  required: true,
+                  message: '此项为必填项',
+                },
+              ],
+            },
+          },
+          {
+            title: '开始付费章节/段落',
+            dataIndex: 'beginPayParagraphNo',
+            valueType: 'select',
+            fieldProps: { showSearch: true, placeholder: '开始付费章节/段落' },
+            hideInForm: paymentType != 2,
+            valueEnum: () => {
+              let arr = bookList;
+              return arr
+                ? new Map(
+                    [...arr]?.map(({ paragraphInfo: { paragraphNo, content } }: any) => [
+                      paragraphNo,
+                      content,
+                    ]),
+                  )
+                : {};
+            },
+            formItemProps: {
+              style: { marginBottom: 10 },
+              rules: [
+                {
+                  required: true,
+                  message: '此项为必填项',
+                },
+              ],
+            },
+          },
+          {
+            valueType: 'dependency',
+            name: ['paymentCategory', 'paymentType'],
+            columns: ({ paymentCategory, paymentType }) => {
+              return [
+                {
+                  title: '收费书币',
+                  dataIndex: 'paymentCoin',
+                  valueType: 'digit',
+                  hideInForm: paymentType === 0 || paymentCategory == 0,
+                  formItemProps: {
+                    style: { marginBottom: 10 },
+                    min: 0,
+                    rules: [
+                      {
+                        required: true,
+                        message: '此项为必填项',
+                      },
+                    ],
+                  },
+                  fieldProps: {
+                    min: 0,
+                    addonAfter: '书币',
+                    precision: 0,
+                  },
+                },
+                {
+                  title: '收费金额',
+                  dataIndex: 'paymentAmount',
+                  valueType: 'money',
+                  hideInForm: paymentType === 0 || paymentCategory == 1,
+                  formItemProps: {
+                    style: { marginBottom: 10 },
+                    rules: [
+                      {
+                        required: true,
+                        message: '此项为必填项',
+                      },
+                    ],
+                  },
+                  fieldProps: {
+                    min: 0,
+                    addonAfter: '元',
+                  },
+                },
+              ];
+            },
+          },
+        ];
+      },
+    },
+    {
+      title: '是否开放给所有分销商',
+      dataIndex: 'publicDistributor',
+      valueType: 'radio',
+      valueEnum: () => {
+        let arr = enumList?.PUBLIC_DISTRIBUTOR?.values;
+        return arr
+          ? new Map([...arr]?.map(({ value, description }: any) => [value, description]))
+          : {};
+      },
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      title: '是否允许分销商配置价格',
+      dataIndex: 'allowDistributorConfig',
+      valueType: 'radio',
+      valueEnum: () => {
+        let arr = enumList?.ALLOW_DISTRIBUTOR_CONFIG?.values;
+        return arr
+          ? new Map([...arr]?.map(({ value, description }: any) => [value, description]))
+          : {};
+      },
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      dataIndex: 'bookDistributorConfig',
+      valueType: 'formList',
+      title: '小说分销商配置',
+      fieldProps: {
+        creatorButtonProps: {
+          creatorButtonText: '新增小说分销商配置',
+        },
+      },
+      formItemProps: {
+        style: { marginBottom: 10 },
+      },
+      columns: [
+        {
+          valueType: 'group',
+          fieldProps: {
+            style: {
+              display: 'flex',
+              alignItems: 'center',
+              flexFlow: 'row',
+            },
+          },
+          columns: [
+            {
+              dataIndex: 'distributorId',
+              fieldProps: { showSearch: true, placeholder: '请选择分销商' },
+              colProps: { span: 10 },
+              formItemProps: {
+                style: { marginBottom: 10, minWidth: 150 },
+                rules: [
+                  {
+                    required: true,
+                    message: '此项为必填项',
+                  },
+                ],
+              },
+              valueEnum: () => {
+                let arr = distributorInfoList;
+                return arr
+                  ? new Map([...arr]?.map(({ id, companyName }: any) => [id, companyName]))
+                  : {};
+              },
+            },
+            {
+              valueType: 'dependency',
+              name: ['configRelations'],
+              columns({ configRelations }) {
+                console.log('configRelations', configRelations);
+                return [
+                  {
+                    valueType: 'checkbox',
+                    dataIndex: 'configRelations',
+                    colProps: { span: 14 },
+                    formItemProps: {
+                      style: { width: 300 },
+                      rules: [
+                        {
+                          required: true,
+                          message: '此项为必填项',
+                        },
+                      ],
+                    },
+                    valueEnum: (a) => {
+                      let arr = enumList?.CONFIG_RELATIONS?.values;
+                      let obj: any = {};
+                      for (let item of arr) {
+                        obj[item.value] = {
+                          text: item.description,
+                          disabled:
+                            configRelations?.length > 0 && !configRelations.includes(item.value),
+                        };
+                      }
+                      return obj;
+                    },
+                  },
+                ];
+              },
+            },
+          ],
+        },
+      ],
+    },
+  ];
+}
+
+export default formConfig;

+ 133 - 4
src/pages/distribution/book/shortBook/index.tsx

@@ -1,4 +1,133 @@
-function Page(){
-    return "短篇小说"
-}
-export default Page
+import { useAjax } from '@/Hook/useAjax';
+import { apiDistributorInfoList } from '@/services/distribution/info';
+import {
+  apiShortBookInfoAllList,
+  apiShortBookInfoBookConfig,
+  apiShortBookInfoConfigDetail,
+  apiShortBookInfoList,
+} from '@/services/distribution/shortBook';
+import {
+  ActionType,
+  BetaSchemaForm,
+  PageContainer,
+  ProFormInstance,
+  ProTable,
+} from '@ant-design/pro-components';
+import { useModel } from '@umijs/max';
+import { message } from 'antd';
+import { useEffect, useRef, useState } from 'react';
+import formConfig from './formConfig';
+import { columns } from './tableConfig';
+
+type DataItem = {
+  name: string;
+  state: string;
+};
+const Page: React.FC = () => {
+  let { state, getLabelAndClassList } = useModel('global');
+  const formRef = useRef<ProFormInstance>();
+  const actionRef = useRef<ActionType>();
+  let [workDirection, setWorkDirection] = useState<any>(null);
+  let [open, setOpen] = useState<any>(null); //新增作者
+  let [editValues, setEditValues] = useState<any>({});
+  // ======================API=======================
+  let DistributorInfoList = useAjax((params) => apiDistributorInfoList(params)); //分销商列表
+  let BookInfoList = useAjax((params) => apiShortBookInfoList(params), { type: 'table' }); //获取书籍列表
+  let BookInfoBookConfig = useAjax((params) => apiShortBookInfoBookConfig(params)); //小说信息配置
+  let BookInfoChapterAllList = useAjax((id) => apiShortBookInfoAllList(id)); //全部列表无分页
+  let BookInfoConfigDetail = useAjax((id) => apiShortBookInfoConfigDetail(id)); //小说配置详情
+
+  // 获取标签和分类
+  useEffect(() => {
+    getLabelAndClassList({ workDirection });
+  }, [workDirection]);
+  // 编辑
+  const edit = (item: any) => {
+    DistributorInfoList.run();
+    BookInfoChapterAllList.run(item.bookId).then((res) => {
+      setOpen(true);
+      BookInfoConfigDetail.run(item.bookId).then((dres) => {
+        console.log(dres.data, item);
+        setEditValues(dres.data);
+        formRef?.current?.setFieldsValue({ ...dres.data });
+      });
+    });
+  };
+  //编辑
+  const submit = async (values: any) => {
+    if (editValues?.id) {
+      values.id = editValues?.id;
+    }
+    if (values.bookDistributorConfig) {
+      values.bookDistributorConfig = values.bookDistributorConfig?.map((item: any) => {
+        return { ...item, bookId: editValues.bookId };
+      });
+    }
+    BookInfoBookConfig.run(values).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('操作成功!');
+        closeForm(false);
+      }
+    });
+    console.log(values);
+  };
+  // 关闭表单弹窗和重置表单内容
+  const closeForm = (b: boolean, values?: any) => {
+    if (!b) {
+      setEditValues({});
+      formRef?.current?.resetFields?.();
+      setOpen(b);
+    } else {
+    }
+  };
+  return (
+    <PageContainer>
+      <ProTable<any, any>
+        headerTitle={'短篇小说配置信息列表'}
+        actionRef={actionRef}
+        rowKey={(r) => r.bookId}
+        // 搜索的配置
+        search={{
+          labelWidth: 90,
+          searchGutter: [10, 15],
+        }}
+        request={async (params) => {
+          return await BookInfoList.run(params);
+        }}
+        scroll={{ x: 'auto' }}
+        columns={columns({
+          authList: state?.authList,
+          labelList: state.labelList,
+          categoryList: state.categoryList,
+          enumList: state?.enumList,
+          setWorkDirection,
+          edit,
+        })}
+        // bordered
+      />
+      <BetaSchemaForm<DataItem>
+        title={!editValues?.id ? '新增作者' : '编辑作者'}
+        formRef={formRef}
+        width={900}
+        open={open}
+        onOpenChange={(b) => {
+          !b && closeForm(b);
+        }}
+        layoutType={'ModalForm'}
+        labelCol={{ span: 8 }}
+        wrapperCol={{ span: 14 }}
+        grid={true}
+        layout="horizontal"
+        onFinish={submit}
+        columns={formConfig({
+          enumList: state?.enumList,
+          bookList: BookInfoChapterAllList?.data?.data,
+          distributorInfoList: DistributorInfoList?.data?.data,
+        })}
+        loading={BookInfoBookConfig?.loading}
+      />
+    </PageContainer>
+  );
+};
+export default Page;

+ 518 - 0
src/pages/distribution/book/shortBook/tableConfig.tsx

@@ -0,0 +1,518 @@
+import { ProColumns } from '@ant-design/pro-components';
+import { Badge, Button, Col, Image, Row, Space, Tag } from 'antd';
+import { createStyles } from 'antd-style';
+const useStyles = createStyles(({ token }) => {
+  return {
+    bookLabel: {
+      color: token.colorTextTertiary,
+    },
+    del: {
+      textDecoration: 'line-through',
+      color: token.colorTextDisabled,
+    },
+    color: {
+      color: token.colorWarningActive,
+    },
+  };
+});
+const brightColors = [
+  '#FF6347', // 番茄红
+  '#FF4500', // 橙红色
+  '#FFD700', // 金黄色
+  '#32CD32', // 鲜绿色
+  '#00FF7F', // 麦绿色
+  '#00CED1', // 暗青色
+  '#4682B4', // 钢蓝色
+  '#6A5ACD', // 鲜紫色
+  '#FF69B4', // 热粉红色
+  '#FF1493', // 深粉红色
+];
+export const columns = (props: {
+  authList?: any[];
+  labelList?: any[];
+  enumList?: { [key: string]: any };
+  categoryList?: any[];
+  setWorkDirection: (w: any) => void;
+  edit: (v: any) => void;
+}): ProColumns<any>[] => {
+  let { authList, labelList, categoryList, enumList, setWorkDirection, edit } = props;
+  let { styles } = useStyles();
+  return [
+    {
+      title: '封面',
+      dataIndex: 'picUrl',
+      key: 'picUrl',
+      width: 90,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      render: (a: any, b: any) => {
+        return (
+          <div style={{ position: 'relative' }}>
+            <Image
+              src={b?.shortBookInfo?.picUrl}
+              style={{ width: 50 }}
+              onError={(e: any) => {
+                e.target.src = localStorage.getItem('nocover');
+              }}
+            />
+          </div>
+        );
+      },
+    },
+    {
+      title: '作品详情',
+      dataIndex: 'authorId',
+      key: 'authorId',
+      ellipsis: true,
+      hideInSearch: true,
+      width: 500,
+      render: (a, b) => {
+        let { bookName, wordCount, labelInfoList, bookStatus, categoryInfo, authorInfo, score } =
+          b?.shortBookInfo;
+        let arr = enumList?.BOOK_STATUS?.values;
+        return (
+          <Row>
+            <Col span={24}>
+              <Space size={[5, 0]} wrap>
+                <a
+                  style={{ fontSize: 14, color: '#337ab7', cursor: 'pointer' }}
+                  onClick={() => {
+                    // lookBook?.({ ...b, pageNum: 1, pageSize: 2 })
+                  }}
+                >
+                  [{categoryInfo?.name}]{bookName}
+                </a>
+                <span style={{ fontSize: 11 }} className={styles.bookLabel}>
+                  [{arr[bookStatus]?.description}]
+                </span>
+                <Space size={[0, 0]}>
+                  {labelInfoList?.map((tags: { id: string; name: string }, index: number) => {
+                    return (
+                      <Tag key={tags?.id} color={brightColors[index]}>
+                        {tags?.name}
+                      </Tag>
+                    );
+                  })}
+                </Space>
+              </Space>
+            </Col>
+            <Col span={24} className={styles.bookLabel}>
+              <span>
+                <span>作者</span>:
+              </span>
+              {authorInfo?.authorName}
+            </Col>
+            <Col span={24} className={styles.bookLabel}>
+              <span>总字数:</span>
+              {wordCount || 0}
+            </Col>
+            <Col span={24} className={styles.bookLabel}>
+              <span>评分:</span>
+              {score || 0}
+            </Col>
+          </Row>
+        );
+      },
+    },
+    {
+      title: '所属频道',
+      dataIndex: 'workDirection',
+      key: 'workDirection',
+      align: 'center',
+      width: 80,
+      ellipsis: true,
+      hideInSearch: true,
+      render: (a: any, b: any) => {
+        return enumList?.WORK_DIRECTION?.values[b.shortBookInfo?.workDirection]?.description;
+      },
+    },
+    {
+      title: '点击量',
+      dataIndex: 'visitCount',
+      key: 'visitCount',
+      width: 90,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      render: (a, b) => {
+        let { visitCount } = b?.shortBookInfo;
+        return visitCount;
+      },
+    },
+    {
+      title: '上架状态',
+      dataIndex: 'shelveStatus',
+      key: 'shelveStatus',
+      width: 90,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      render: (a: any, b) => {
+        let arr: any = enumList?.SHELVE_STATUS?.values;
+        let { shelveStatus } = b?.shortBookInfo;
+        return (
+          arr[shelveStatus]?.description && (
+            <Badge
+              text={arr[shelveStatus]?.description}
+              status={shelveStatus == 0 ? 'processing' : 'default'}
+            />
+          )
+        );
+      },
+    },
+    {
+      title: '来源',
+      dataIndex: 'source',
+      key: 'source',
+      width: 90,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      render: (a: any, b) => {
+        let { source } = b?.shortBookInfo;
+        let arr: any = new Map(
+          enumList?.SOURCE?.values?.map(({ value, description }: any) => [value, description]),
+        );
+        return arr.get(source);
+      },
+    },
+    {
+      title: 'configId',
+      dataIndex: 'configId',
+      key: 'configId',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+      render: (a) => {
+        return a;
+      },
+    },
+    {
+      title: '是否开放给所有分销商',
+      dataIndex: 'publicDistributor',
+      key: 'publicDistributor',
+      align: 'center',
+      width: 150,
+      ellipsis: true,
+      hideInSearch: true,
+      render: (a, b) => {
+        let isHot = a ? '是' : '否';
+        return <Badge status={a ? 'processing' : 'default'} text={isHot} />;
+      },
+    },
+    {
+      title: '是否允许分销商配置价格',
+      dataIndex: 'allowDistributorConfig',
+      key: 'allowDistributorConfig',
+      align: 'center',
+      width: 150,
+      ellipsis: true,
+      hideInSearch: true,
+      render: (a, b) => {
+        let isHot = a ? '是' : '否';
+        return <Badge status={a ? 'processing' : 'default'} text={isHot} />;
+      },
+    },
+    {
+      title: '付费类型',
+      dataIndex: 'paymentType',
+      key: 'paymentType',
+      width: 150,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      valueEnum: () => {
+        let arr = enumList?.PAYMENT_TYPE?.values;
+        return arr
+          ? new Map([...arr]?.map(({ value, description }: any) => [value, description]))
+          : {};
+      },
+    },
+    {
+      title: '收费类型',
+      dataIndex: 'paymentOption',
+      key: 'paymentOption',
+      width: 100,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      valueEnum: () => {
+        let arr = enumList?.PAYMENT_OPTION?.values;
+        return arr
+          ? new Map([...arr]?.map(({ value, description }: any) => [value, description]))
+          : {};
+      },
+    },
+    {
+      title: 'vip限免',
+      dataIndex: 'vipFree',
+      key: 'vipFree',
+      width: 150,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      render: (a, b) => {
+        return <Badge text={a ? '是' : '否'} status={a ? 'processing' : 'default'} />;
+      },
+    },
+    {
+      title: '收费金额',
+      dataIndex: 'paymentAmount',
+      key: 'paymentAmount',
+      width: 150,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      render: (a, b) => {
+        return (
+          <span
+            style={
+              b?.paymentCategory === 1 ? { textDecoration: 'line-through', color: '#bab8b8' } : {}
+            }
+          >
+            {b?.paymentAmount || '-'}
+          </span>
+        );
+      },
+    },
+    {
+      title: '收费书币',
+      dataIndex: 'paymentCoin',
+      key: 'paymentCoin',
+      width: 150,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      render: (a, b) => {
+        return (
+          <span
+            style={
+              b?.paymentCategory === 0 ? { textDecoration: 'line-through', color: '#bab8b8' } : {}
+            }
+          >
+            {b?.paymentCoin || '-'}
+          </span>
+        );
+      },
+    },
+    {
+      title: '操作',
+      dataIndex: 'cz',
+      key: 'cz',
+      width: 160,
+      align: 'center',
+      className: 'table_fixed',
+      hideInSearch: true,
+      render: (a: any, b: any) => {
+        return (
+          <Space>
+            <Button
+              onClick={() => {
+                edit(b);
+              }}
+              type="link"
+            >
+              信息配置
+            </Button>
+          </Space>
+        );
+      },
+    },
+    // 搜索条件
+    {
+      title: '小说名称',
+      dataIndex: 'bookName',
+      valueType: 'text',
+      hideInTable: true,
+      fieldProps: { placeholder: '请输入小说名称' },
+      colSize: 1,
+    },
+    {
+      title: '作者',
+      dataIndex: 'authorId',
+      valueType: 'select',
+      hideInTable: true,
+      fieldProps: { showSearch: true, placeholder: '请选择作者' },
+      colSize: 1,
+      valueEnum: new Map(authList?.map(({ id, authorName }) => [id, authorName])),
+    },
+    {
+      title: '频道',
+      dataIndex: 'workDirection',
+      valueType: 'segmented',
+      hideInTable: true,
+      fieldProps: {
+        type: 'primary',
+        style: { width: 'auto' },
+        onChange: (value) => {
+          setWorkDirection(value);
+        },
+      },
+      colSize: 3,
+      initialValue: '',
+      valueEnum: () => {
+        let arr = enumList?.WORK_DIRECTION?.values;
+        return arr
+          ? new Map(
+              [{ value: '', description: '全部' }, ...arr]?.map(({ value, description }: any) => [
+                value,
+                description,
+              ]),
+            )
+          : {};
+      },
+    },
+    {
+      title: '分类',
+      dataIndex: 'categoryId',
+      valueType: 'segmented',
+      hideInTable: true,
+      hideInSearch: !labelList,
+      fieldProps: { type: 'primary', style: { width: 'auto' } },
+      colSize: 3,
+      initialValue: '',
+      valueEnum: () => {
+        let arr = categoryList || [];
+        return new Map([{ id: '', name: '全部' }, ...arr]?.map((item) => [item.id, item.name]));
+      },
+    },
+    {
+      title: '标签',
+      dataIndex: 'labelIds',
+      valueType: 'segmented',
+      hideInTable: true,
+      hideInSearch: !labelList,
+      fieldProps: { type: 'primary', style: { width: 'auto' } },
+      colSize: 3,
+      initialValue: '',
+      valueEnum: () => {
+        let arr = labelList || [];
+        return new Map([{ id: '', name: '全部' }, ...arr]?.map((item) => [item.id, item.name]));
+      },
+    },
+    {
+      title: '来源',
+      dataIndex: 'source',
+      valueType: 'segmented',
+      hideInTable: true,
+      fieldProps: { type: 'primary', style: { width: 'auto' } },
+      colSize: 3,
+      initialValue: '',
+      valueEnum: () => {
+        let arr = enumList?.SOURCE?.values;
+        return arr
+          ? new Map(
+              [{ value: '', description: '全部' }, ...arr]?.map(({ value, description }: any) => [
+                value,
+                description,
+              ]),
+            )
+          : {};
+      },
+    },
+    {
+      title: '连载',
+      dataIndex: 'bookStatus',
+      valueType: 'segmented',
+      hideInTable: true,
+      fieldProps: { type: 'primary', style: { width: 'auto' } },
+      colSize: 3,
+      initialValue: '',
+      valueEnum: () => {
+        let arr = enumList?.BOOK_STATUS?.values;
+        return arr
+          ? new Map(
+              [{ value: '', description: '全部' }, ...arr]?.map(({ value, description }: any) => [
+                value,
+                description,
+              ]),
+            )
+          : {};
+      },
+    },
+    {
+      title: '上架',
+      dataIndex: 'shelveStatus',
+      valueType: 'segmented',
+      hideInTable: true,
+      fieldProps: { type: 'primary', style: { width: 'auto' } },
+      colSize: 3,
+      initialValue: '',
+      valueEnum: () => {
+        let arr = enumList?.SHELVE_STATUS?.values;
+        return arr
+          ? new Map(
+              [{ value: '', description: '全部' }, ...arr]?.map(({ value, description }: any) => [
+                value,
+                description,
+              ]),
+            )
+          : {};
+      },
+    },
+    {
+      title: '付费',
+      dataIndex: 'paymentType',
+      valueType: 'segmented',
+      hideInTable: true,
+      fieldProps: { type: 'primary', style: { width: 'auto' } },
+      colSize: 3,
+      initialValue: '',
+      valueEnum: () => {
+        let arr = enumList?.PAYMENT_TYPE?.values;
+        return arr
+          ? new Map(
+              [{ value: '', description: '全部' }, ...arr]?.map(({ value, description }: any) => [
+                value,
+                description,
+              ]),
+            )
+          : {};
+      },
+    },
+    {
+      title: 'VIP',
+      dataIndex: 'vipFree',
+      valueType: 'segmented',
+      hideInTable: true,
+      fieldProps: { type: 'primary', style: { width: 'auto' } },
+      colSize: 3,
+      initialValue: '',
+      valueEnum: () => {
+        let arr = enumList?.VIP_FREE?.values;
+        return arr
+          ? new Map(
+              [{ value: '', description: '全部' }, ...arr]?.map(({ value, description }: any) => [
+                value,
+                description,
+              ]),
+            )
+          : {};
+      },
+    },
+    {
+      title: '字数',
+      dataIndex: 'wordCount',
+      valueType: 'segmented',
+      hideInTable: true,
+      fieldProps: { type: 'primary', style: { width: 'auto' }, block: true },
+      colSize: 3,
+      initialValue: '',
+      valueEnum: {
+        '': '全部',
+        '0-2': '2万内',
+        '2-5': '2-5万',
+        '5-10': '5-10万',
+        '10-20': '10-20万',
+        '20-40': '20-40万',
+        '40-100': '40-100万',
+        '100-150': '100-150万',
+        '150-200': '150-200万',
+        '200-300': '200-300万',
+        '300-0': '300万以上',
+      },
+    },
+  ];
+};

+ 150 - 0
src/pages/distribution/distributor/info/formConfig.tsx

@@ -0,0 +1,150 @@
+import { ProFormColumnsType } from '@ant-design/pro-components';
+
+export function formConfig(): ProFormColumnsType<{
+  name: string;
+  state: string;
+}>[] {
+  return [
+    {
+      title: '分销公司名称',
+      dataIndex: 'companyName',
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      title: '分销公司描述',
+      dataIndex: 'companyDesc',
+      valueType: 'textarea',
+      formItemProps: {
+        style: { marginBottom: 10 },
+      },
+    },
+    {
+      title: '分销公司地址',
+      dataIndex: 'companyAddress',
+      formItemProps: {
+        style: { marginBottom: 10 },
+      },
+    },
+    {
+      title: '联系人姓名',
+      dataIndex: 'contactName',
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      title: '联系人电话',
+      dataIndex: 'contactPhone',
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      title: '联系人邮箱',
+      dataIndex: 'contactEmail',
+      formItemProps: {
+        style: { marginBottom: 10 },
+      },
+    },
+    {
+      title: '备注',
+      dataIndex: 'remark',
+      valueType: 'textarea',
+      formItemProps: {
+        style: { marginBottom: 10 },
+      },
+    },
+  ];
+}
+
+export function itemFormConfig(
+  list: { id: number; nickname: string }[],
+  isEdit: boolean,
+): ProFormColumnsType<{
+  name: string;
+  state: string;
+}>[] {
+  return [
+    {
+      title: '账号昵称',
+      dataIndex: 'nickname',
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      title: '账号密码',
+      dataIndex: 'password',
+      hideInForm: isEdit,
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      title: '联系人电话',
+      dataIndex: 'phoneNum',
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      title: '子账号列表',
+      dataIndex: 'subAccountList',
+      valueType: 'select',
+      formItemProps: {
+        style: { marginBottom: 10 },
+      },
+      fieldProps: {
+        mode: 'multiple',
+        showSearch: true,
+      },
+      valueEnum: new Map(list?.map((item) => [item.id, item.nickname])),
+    },
+    {
+      title: '备注',
+      dataIndex: 'remark',
+      valueType: 'textarea',
+      formItemProps: {
+        style: { marginBottom: 10 },
+      },
+    },
+  ];
+}

+ 153 - 4
src/pages/distribution/distributor/info/index.tsx

@@ -1,4 +1,153 @@
-function Page(){
-    return "分销商信息"
-}
-export default Page
+import { useAjax } from '@/Hook/useAjax';
+import {
+  apiDistributorInfo,
+  apiDistributorInfoPageList,
+  apiDistributorInfoRemove,
+  apiDistributorInfoUpdate,
+} from '@/services/distribution/info';
+import { PlusCircleOutlined } from '@ant-design/icons';
+import {
+  ActionType,
+  BetaSchemaForm,
+  PageContainer,
+  ProFormInstance,
+  ProTable,
+} from '@ant-design/pro-components';
+import { useModel } from '@umijs/max';
+import { Button, message } from 'antd';
+import { useEffect, useRef, useState } from 'react';
+import { formConfig } from './formConfig';
+import Item from './item';
+import { columns } from './tableConfig';
+type DataItem = {
+  name: string;
+  state: string;
+};
+const Page: React.FC = () => {
+  let { state, getLabelAndClassList } = useModel('global');
+  let [open, setOpen] = useState<any>(null); //付费配置
+  let [editValues, setEditValues] = useState<any>({});
+  let [workDirection, setWorkDirection] = useState<any>(null);
+  const [openBook, setOpneBook] = useState<any>(null); //阅读小说
+  const [editSelectedRow, setEditSelectedRow] = useState<any[]>([]); //小说列表选择
+
+  // ======================API=======================
+  let DistributorInfoPageList = useAjax((params) => apiDistributorInfoPageList(params), {
+    type: 'table',
+  }); //分销商分页列表
+  let DistributorInfoUpdate = useAjax((params) => apiDistributorInfoUpdate(params)); //修改
+  let DistributorInfo = useAjax((params) => apiDistributorInfo(params)); //新增
+  let DistributorInfoRemove = useAjax((distributorId) => apiDistributorInfoRemove(distributorId)); //分销商删除
+  const formRef = useRef<ProFormInstance>();
+  const actionRef = useRef<ActionType>();
+
+  // 获取标签和分类
+  useEffect(() => {
+    getLabelAndClassList({ workDirection });
+  }, [workDirection]);
+  // 删除or批量删除
+  const del = (id: any) => {
+    DistributorInfoRemove.run(id).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('删除成功');
+        setEditSelectedRow([]);
+      }
+    });
+  };
+  // 提交表单
+  const submit = async (values: any) => {
+    let api = editValues?.id ? DistributorInfoUpdate : DistributorInfo;
+    if (editValues?.id) {
+      values.id = editValues?.id;
+    }
+    api.run(values).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('操作成功!');
+        closeForm(false);
+      }
+    });
+  };
+  // 关闭表单弹窗和重置表单内容
+  const closeForm = (b: boolean, values?: any) => {
+    if (!b) {
+      setEditValues({});
+      formRef?.current?.resetFields?.();
+      setOpen(b);
+    } else {
+      setEditValues(values);
+      setWorkDirection(values.workDirection);
+      setTimeout(() => {
+        formRef?.current?.setFieldsValue(values);
+      }, 100);
+      setOpen(b);
+    }
+  };
+  return (
+    <PageContainer title={false} tabProps={{ type: 'card' }}>
+      <ProTable<any, any>
+        // 实例
+        actionRef={actionRef}
+        // 标题
+        headerTitle={'短篇小说列表'}
+        // 唯一key
+        rowKey={(r) => r.id}
+        // 按钮
+        toolBarRender={() => {
+          return [
+            <Button
+              type="primary"
+              onClick={() => {
+                setOpen(true);
+              }}
+            >
+              <PlusCircleOutlined />
+              新增分销商
+            </Button>,
+          ];
+        }}
+        //宽度自适应
+        scroll={{ x: true }}
+        // 加载
+        loading={DistributorInfoPageList?.loading}
+        // 搜索的配置
+        search={{
+          labelWidth: 120,
+          searchGutter: [10, 15],
+        }}
+        // 数据请求
+        request={async (params) => {
+          return await DistributorInfoPageList.run(params);
+        }}
+        // 表
+        columns={columns({ del, edit: closeForm, setEditValues })}
+      />
+      {/* 新增分销商 */}
+      <BetaSchemaForm<DataItem>
+        layout="horizontal"
+        title={!editValues?.id ? '新增分销商' : '编辑分销商'}
+        formRef={formRef}
+        open={open}
+        onOpenChange={(b) => {
+          !b && closeForm(b);
+        }}
+        layoutType={'ModalForm'}
+        rowProps={{
+          gutter: [16, 16],
+        }}
+        labelCol={{ span: 6 }}
+        colProps={{
+          span: 20,
+        }}
+        grid={true}
+        onFinish={submit}
+        columns={formConfig()}
+        loading={DistributorInfo?.loading || DistributorInfoUpdate?.loading}
+      />
+      {/* 账号管理 */}
+      <Item data={editValues} onClose={setEditValues} />
+    </PageContainer>
+  );
+};
+export default Page;

+ 170 - 0
src/pages/distribution/distributor/info/item.tsx

@@ -0,0 +1,170 @@
+import { useAjax } from '@/Hook/useAjax';
+import {
+  apiDistributorAccount,
+  apiDistributorAccountInfoRemove,
+  apiDistributorAccountList,
+  apiDistributorAccountPageList,
+  apiDistributorAccountUpdate,
+  apiDistributorAccountUpdateEnabled,
+} from '@/services/distribution/accountInfo';
+import { apiDistributorInfoList } from '@/services/distribution/info';
+import { PlusCircleOutlined } from '@ant-design/icons';
+import { ActionType, BetaSchemaForm, ProFormInstance, ProTable } from '@ant-design/pro-components';
+import { Button, Drawer, message } from 'antd';
+import { useRef, useState } from 'react';
+import { itemFormConfig } from './formConfig';
+import { itemColumns } from './tableConfig';
+type DataItem = {
+  name: string;
+  state: string;
+};
+type Props = {
+  data: any;
+  onClose: (v: any) => void;
+};
+const Page: React.FC<Props> = (props) => {
+  let { data, onClose } = props;
+  let [open, setOpen] = useState<any>(null); //新建弹窗
+  const [uploadOpen, setUploadOpen] = useState(false);
+  let [editValues, setEditValues] = useState<any>({});
+  // ======================API=======================
+  let DistributorInfoList = useAjax((params) => apiDistributorInfoList(params)); //分销商列表
+  let DistributorAccountUpdate = useAjax((params) => apiDistributorAccountUpdate(params)); //修改
+  let DistributorAccount = useAjax((params) => apiDistributorAccount(params)); //新增
+  let DistributorAccountPageList = useAjax((params) => apiDistributorAccountPageList(params), {
+    type: 'table',
+  }); //分销商账号分页列表
+  let DistributorAccountList = useAjax((distributorId) => apiDistributorAccountList(distributorId)); //分销商账号全部列表
+  let DistributorAccountInfoRemove = useAjax((distributorId) =>
+    apiDistributorAccountInfoRemove(distributorId),
+  ); //分销商账号删除
+  let DistributorAccountUpdateEnabled = useAjax((distributorId) =>
+    apiDistributorAccountUpdateEnabled(distributorId),
+  ); //分销商账号状态更新
+  const formRef = useRef<ProFormInstance>();
+  const actionRef = useRef<ActionType>();
+  // // 起始获取列表
+  // useEffect(() => {
+  //     DistributorInfoList.run()
+  // }, [])
+  // 删除or批量删除
+  const del = (id: any) => {
+    DistributorAccountInfoRemove.run(id).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('删除成功');
+      }
+    });
+  };
+  // 状态更新
+  const stateUpdata = (id: any, ck: boolean) => {
+    return DistributorAccountUpdateEnabled.run({ id, enabled: ck }).then((res) => {
+      if (res.data) {
+        actionRef?.current?.reload();
+        message.success(ck ? '启动成功' : '停用成功');
+      }
+      return true;
+    });
+  };
+  // 提交表单
+  const submit = async (values: any) => {
+    let api = editValues?.id ? DistributorAccountUpdate : DistributorAccount;
+    if (editValues?.id) {
+      values.id = editValues?.id;
+    }
+    values.distributorId = data.id;
+    if (!values.subAccountList) {
+      values.subAccountList = [];
+    }
+    api.run(values).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('操作成功!');
+        closeForm(false);
+      }
+    });
+  };
+  // 关闭表单弹窗和重置表单内容
+  const closeForm = (b: boolean, values?: any) => {
+    if (!b) {
+      setEditValues({});
+      formRef?.current?.resetFields?.();
+      setOpen(b);
+    } else {
+      DistributorAccountList.run(data.id);
+      setEditValues(values);
+      setTimeout(() => {
+        formRef.current?.setFieldsValue({
+          ...values,
+          subAccountList: values?.subDistributorAccountList?.map((item: { id: any }) => item.id),
+        });
+      }, 100);
+      setOpen(b);
+    }
+  };
+
+  return (
+    <Drawer
+      open={data?.id}
+      placement="right"
+      onClose={() => {
+        onClose({});
+      }}
+      width={'75%'}
+      footer={null}
+      destroyOnClose={true}
+      closeIcon={false}
+    >
+      <ProTable<any, any>
+        headerTitle={'《' + data?.companyName + '》账号列表'}
+        actionRef={actionRef}
+        rowKey={(r) => r.id}
+        search={{
+          labelWidth: 120,
+        }}
+        scroll={{ x: 'auto' }}
+        params={{ distributorId: data?.id }}
+        request={async (params) => {
+          if (params.distributorId) {
+            return await DistributorAccountPageList.run(params);
+          }
+        }}
+        toolBarRender={() => {
+          return [
+            <Button
+              type="primary"
+              onClick={() => {
+                DistributorAccountList.run(data.id);
+                setOpen(true);
+              }}
+            >
+              <PlusCircleOutlined />
+              新增分销账号
+            </Button>,
+          ];
+        }}
+        columns={itemColumns({ del, closeForm, stateUpdata })}
+        loading={DistributorAccountPageList?.loading || DistributorAccountUpdateEnabled?.loading}
+      />
+      {/* 新增段落 */}
+      <BetaSchemaForm<DataItem>
+        layout="horizontal"
+        title={!editValues?.id ? '新增分销账号' : '编辑分销账号'}
+        formRef={formRef}
+        open={open}
+        onOpenChange={(b) => {
+          !b && closeForm(b);
+        }}
+        layoutType={'ModalForm'}
+        grid={true}
+        labelCol={{ span: 6 }}
+        colProps={{ span: 20 }}
+        width={500}
+        onFinish={submit}
+        columns={itemFormConfig(DistributorAccountList?.data?.data, !!editValues?.id)}
+        loading={DistributorAccount?.loading || DistributorAccountUpdate?.loading}
+      />
+    </Drawer>
+  );
+};
+export default Page;

+ 255 - 0
src/pages/distribution/distributor/info/tableConfig.tsx

@@ -0,0 +1,255 @@
+import { ProColumns } from '@ant-design/pro-components';
+import { Button, Popconfirm, Space, Switch } from 'antd';
+
+export const columns = (props: {
+  del: (id: any) => void;
+  edit: (b: boolean, v: any) => void;
+  setEditValues: (v: any) => void;
+}): ProColumns<any>[] => {
+  let { del, edit, setEditValues } = props;
+  return [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      key: 'id',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '分销公司名称',
+      dataIndex: 'companyName',
+      key: 'companyName',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+    },
+    {
+      title: '分销公司描述',
+      dataIndex: 'companyDesc',
+      key: 'companyDesc',
+      align: 'center',
+      width: 80,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '分销公司地址',
+      dataIndex: 'companyAddress',
+      key: 'companyAddress',
+      width: 80,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+    },
+    {
+      title: '联系人姓名',
+      dataIndex: 'contactName',
+      key: 'contactName',
+      width: 80,
+      align: 'center',
+      ellipsis: true,
+    },
+    {
+      title: '联系人电话',
+      dataIndex: 'contactPhone',
+      key: 'contactPhone',
+      width: 80,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '联系人邮箱',
+      dataIndex: 'contactEmail',
+      key: 'contactEmail',
+      width: 80,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '备注',
+      dataIndex: 'remark',
+      key: 'remark',
+      width: 80,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '操作',
+      dataIndex: 'cz',
+      key: 'cz',
+      width: 160,
+      fixed: 'right',
+      align: 'center',
+      hideInSearch: true,
+      render: (a, b) => {
+        return (
+          <Space>
+            <Button
+              onClick={() => {
+                edit(true, b);
+              }}
+              type="link"
+              size="small"
+            >
+              编辑
+            </Button>
+            <Popconfirm
+              title={
+                <div>
+                  确定要删除<span style={{ color: 'red' }}>{b.companyName}</span>分销商?
+                </div>
+              }
+              onConfirm={() => {
+                del(b.id);
+              }}
+            >
+              <Button type="link" size="small" danger>
+                删除
+              </Button>
+            </Popconfirm>
+            <Button
+              onClick={() => {
+                setEditValues(b);
+              }}
+              type="link"
+              size="small"
+            >
+              账号管理
+            </Button>
+          </Space>
+        );
+      },
+    },
+  ];
+};
+
+export const itemColumns = (props: {
+  del: (id: any) => void;
+  closeForm: (b: boolean, v: any) => void;
+  stateUpdata: (id: any, ck: boolean) => Promise<any>;
+}): ProColumns<any>[] => {
+  let { del, closeForm, stateUpdata } = props;
+  return [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      key: 'id',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '分销商名称',
+      dataIndex: 'distributorName',
+      key: 'distributorName',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '账号昵称',
+      dataIndex: 'nickname',
+      key: 'nickname',
+      align: 'center',
+      width: 80,
+      ellipsis: true,
+    },
+    {
+      title: '子账号',
+      dataIndex: 'subDistributorAccountList',
+      key: 'subDistributorAccountList',
+      align: 'center',
+      width: 80,
+      ellipsis: true,
+      hideInSearch: true,
+      render(dom, entity, index, action, schema) {
+        return entity?.subDistributorAccountList
+          ?.map((item: { nickname: any }) => item.nickname)
+          ?.join();
+      },
+    },
+    {
+      title: '手机号',
+      dataIndex: 'phoneNum',
+      key: 'phoneNum',
+      width: 80,
+      ellipsis: true,
+      align: 'center',
+    },
+    {
+      title: '备注',
+      dataIndex: 'remark',
+      key: 'remark',
+      width: 80,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '状态',
+      dataIndex: 'enabled',
+      key: 'enabled',
+      width: 80,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+      render: (a: any, b: any) => {
+        return (
+          <Switch
+            checkedChildren="正常"
+            unCheckedChildren="停用"
+            checked={b.enabled}
+            onChange={(ck) => {
+              stateUpdata(b.id, ck);
+            }}
+          />
+        );
+      },
+    },
+    {
+      title: '操作',
+      dataIndex: 'cz',
+      key: 'cz',
+      width: 160,
+      fixed: 'right',
+      align: 'center',
+      hideInSearch: true,
+      render: (a: any, b: any) => {
+        return (
+          <Space>
+            <Button
+              onClick={() => {
+                closeForm(true, b);
+              }}
+              type="link"
+              size="small"
+            >
+              编辑
+            </Button>
+            <Popconfirm
+              title={
+                <div>
+                  确定要删除<span style={{ color: 'red' }}>{b.nickname}</span>账号?
+                </div>
+              }
+              onConfirm={() => {
+                del(b.id);
+              }}
+            >
+              <Button type="link" size="small" danger>
+                删除
+              </Button>
+            </Popconfirm>
+          </Space>
+        );
+      },
+    },
+  ];
+};

+ 3 - 3
src/pages/distribution/miniprogram/mimiModule/index.tsx

@@ -1,4 +1,4 @@
-function Page(){
-    return "小程序组件配置"
+function Page() {
+  return '小程序组件配置';
 }
-export default Page
+export default Page;

+ 158 - 0
src/pages/distribution/miniprogram/weChatInfo/formConfig.tsx

@@ -0,0 +1,158 @@
+import { ProFormColumnsType } from '@ant-design/pro-components';
+function formConfig(
+  enumList?: { [key: string]: any },
+  getWechatMchAll?: any[],
+): ProFormColumnsType<{
+  name: string;
+  state: string;
+}>[] {
+  return [
+    {
+      title: '小程序appId',
+      dataIndex: 'wechatAppId',
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      title: '小程序秘钥',
+      dataIndex: 'appSecret',
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      title: '商户',
+      dataIndex: 'mchId',
+      valueType: 'select',
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+      valueEnum: () => {
+        let arr = getWechatMchAll;
+        return arr ? new Map([...arr]?.map(({ id, mchName }: any) => [id, mchName])) : {};
+      },
+    },
+    {
+      title: '小程序名称',
+      dataIndex: 'appName',
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      title: '小程序类型',
+      dataIndex: 'appCategory',
+      valueType: 'radio',
+      formItemProps: {
+        initialValue: 2,
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+      valueEnum: () => {
+        let arr = enumList?.APP_CATEGORY?.values;
+        return arr
+          ? new Map([...arr]?.map(({ value, description }: any) => [value, description]))
+          : {};
+      },
+    },
+    {
+      title: '小程序首页链接',
+      dataIndex: 'homePage',
+      formItemProps: {
+        style: { marginBottom: 10 },
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      title: '小程序版本号',
+      dataIndex: 'appVersion',
+      formItemProps: {
+        style: { marginBottom: 10 },
+      },
+    },
+    {
+      dataIndex: 'configParamList',
+      valueType: 'formList',
+      colProps: {
+        offset: 6,
+      },
+      fieldProps: {
+        creatorButtonProps: {
+          creatorButtonText: '新增小程序配置',
+        },
+      },
+      formItemProps: {
+        style: { marginBottom: 10 },
+      },
+      columns: [
+        {
+          valueType: 'dependency',
+          name: ['configParamList'],
+          formItemProps: {
+            style: { marginBottom: 10 },
+          },
+          columns: ({ configParamList }) => {
+            return [
+              {
+                valueType: 'group',
+                columns: [
+                  {
+                    formItemProps: {
+                      style: { marginBottom: 10 },
+                    },
+                  },
+                  {
+                    formItemProps: {
+                      style: { marginBottom: 10 },
+                    },
+                  },
+                ],
+              },
+            ];
+          },
+        },
+      ],
+    },
+    {
+      title: '备注',
+      dataIndex: 'remark',
+    },
+  ];
+}
+
+export default formConfig;

+ 210 - 4
src/pages/distribution/miniprogram/weChatInfo/index.tsx

@@ -1,4 +1,210 @@
-function Page(){
-    return "小程序信息"
-}
-export default Page
+import { useAjax } from '@/Hook/useAjax';
+import { apiDistributorInfoList } from '@/services/distribution/info';
+import {
+  apiWxAppInfo,
+  apiWxAppInfoPageList,
+  apiWxAppInfoRemove,
+  apiWxAppInfoUpdate,
+  apiWxAppInfoUpdateEnabled,
+  apiWxAppInfoWxAppConfigAddOrEdit,
+  wechatMchAll,
+} from '@/services/distribution/weChatInfo';
+import { PlusCircleOutlined } from '@ant-design/icons';
+import {
+  ActionType,
+  BetaSchemaForm,
+  PageContainer,
+  ProFormInstance,
+  ProTable,
+} from '@ant-design/pro-components';
+import { useModel } from '@umijs/max';
+import { Button, message } from 'antd';
+import { useCallback, useEffect, useRef, useState } from 'react';
+import formConfig from './formConfig';
+import { columns } from './tableConfig';
+
+type DataItem = {
+  name: string;
+  state: string;
+};
+const Page: React.FC = () => {
+  const formRef = useRef<ProFormInstance>();
+  const assignRormRef = useRef<ProFormInstance>();
+  const actionRef = useRef<ActionType>();
+  let { state } = useModel('global');
+  let [open, setOpen] = useState<any>(null); //新增
+  let [assignOpen, setAssignOpen] = useState(false);
+  let [editValues, setEditValues] = useState<any>({});
+  let WxAppInfoUpdateEnabled = useAjax((params) => apiWxAppInfoUpdateEnabled(params)); //微信小程序状态更新
+  let WxAppInfoUpdate = useAjax((params) => apiWxAppInfoUpdate(params)); //微信小程序信息更新
+  let WxAppInfo = useAjax((params) => apiWxAppInfo(params)); //微信小程序信息新增
+  let WxAppInfoPageList = useAjax((params) => apiWxAppInfoPageList(params), { type: 'table' }); //微信小程序分页列表
+  let WxAppInfoRemove = useAjax((distributorId) => apiWxAppInfoRemove(distributorId)); //小程序删除
+  let getWechatMchAll = useAjax(() => wechatMchAll()); //商户列表
+  let WxAppInfoWxAppConfigAddOrEdit = useAjax((distributorId) =>
+    apiWxAppInfoWxAppConfigAddOrEdit(distributorId),
+  ); //指派
+  let DistributorInfoList = useAjax((params) => apiDistributorInfoList(params)); //分销商列表
+  useEffect(() => {
+    getWechatMchAll.run();
+  }, []);
+  // 删除
+  const del = (id: any) => {
+    WxAppInfoRemove.run(id).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('删除成功');
+      }
+    });
+  };
+  // 状态更新
+  const stateUpdata = (id: any, ck: boolean) => {
+    WxAppInfoUpdateEnabled.run({ id, enabled: ck }).then((res) => {
+      if (res.data) {
+        actionRef?.current?.reload();
+        message.success(ck ? '启动成功' : '停用成功');
+      }
+    });
+  };
+  // 指派
+  const mimiAssign = useCallback(
+    (params?: any) => {
+      DistributorInfoList.run();
+      setEditValues(params);
+      setAssignOpen(true);
+      assignRormRef?.current?.setFieldsValue({ distributorId: params?.distributorInfo?.id });
+    },
+    [WxAppInfoPageList],
+  );
+  const assign = async (values: any) => {
+    values.appId = editValues.id;
+    WxAppInfoWxAppConfigAddOrEdit.run(values).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('操作成功!');
+        setEditValues({});
+        setAssignOpen(false);
+      }
+    });
+  };
+  // 新增 or 编辑
+  const submit = async (values: any) => {
+    let api = editValues?.id ? WxAppInfoUpdate : WxAppInfo;
+    if (editValues?.id) {
+      values.id = editValues?.id;
+    }
+    api.run(values).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('操作成功!');
+        closeForm(false);
+      }
+    });
+    console.log(values);
+  };
+  // 关闭表单弹窗和重置表单内容
+  const closeForm = (b: boolean, values?: any) => {
+    if (!b) {
+      setEditValues({});
+      formRef?.current?.resetFields?.();
+      setOpen(b);
+    } else {
+      setEditValues(values);
+      setTimeout(() => {
+        formRef?.current?.setFieldsValue({ ...values, mchId: values?.mchInfo?.id });
+      }, 100);
+      setOpen(b);
+    }
+  };
+  return (
+    <PageContainer>
+      <ProTable<any, any>
+        headerTitle={'小程序列表'}
+        actionRef={actionRef}
+        rowKey={(r) => r.id}
+        search={{
+          labelWidth: 120,
+        }}
+        scroll={{ x: 'auto' }}
+        request={async (params) => {
+          return await WxAppInfoPageList.run(params);
+        }}
+        toolBarRender={() => {
+          return [
+            <Button
+              type="primary"
+              onClick={() => {
+                setOpen(true);
+              }}
+            >
+              <PlusCircleOutlined />
+              新增小程序
+            </Button>,
+          ];
+        }}
+        columns={columns(closeForm, del, stateUpdata, mimiAssign)}
+        // bordered
+      />
+      {/* 新增修改 */}
+      <BetaSchemaForm<DataItem>
+        title={!editValues?.id ? '新增小程序' : '编辑小程序'}
+        formRef={formRef}
+        width={600}
+        open={open}
+        onOpenChange={(b) => {
+          !b && closeForm(b);
+        }}
+        layoutType={'ModalForm'}
+        labelCol={{ span: 6 }}
+        wrapperCol={{ span: 14 }}
+        grid={true}
+        rowProps={{ gutter: [10, 10] }}
+        layout="horizontal"
+        onFinish={submit}
+        columns={formConfig(state?.enumList, getWechatMchAll?.data?.data)}
+        loading={WxAppInfoUpdate?.loading || WxAppInfo?.loading}
+      />
+      {/*指派  */}
+      <BetaSchemaForm<DataItem>
+        title={editValues?.appName + '小程序指派'}
+        formRef={assignRormRef}
+        width={400}
+        open={assignOpen}
+        onOpenChange={(b) => {
+          if (!b) {
+            setEditValues({});
+            setAssignOpen(false);
+          }
+        }}
+        layoutType={'ModalForm'}
+        labelCol={{ span: 6 }}
+        wrapperCol={{ span: 14 }}
+        grid={true}
+        rowProps={{ gutter: [20, 20] }}
+        layout="horizontal"
+        onFinish={assign}
+        columns={[
+          {
+            title: '分销商',
+            dataIndex: 'distributorId',
+            formItemProps: {
+              style: { marginTop: 20 },
+              rules: [
+                {
+                  required: true,
+                  message: '此项为必填项',
+                },
+              ],
+            },
+            valueEnum: () => {
+              let arr = DistributorInfoList?.data?.data;
+              return new Map(arr?.map(({ id, companyName }: any) => [id, companyName]));
+            },
+          },
+        ]}
+        loading={WxAppInfoWxAppConfigAddOrEdit?.loading}
+      />
+    </PageContainer>
+  );
+};
+export default Page;

+ 186 - 0
src/pages/distribution/miniprogram/weChatInfo/tableConfig.tsx

@@ -0,0 +1,186 @@
+import { ProColumns } from '@ant-design/pro-components';
+import { Button, Popconfirm, Space, Switch, Tooltip } from 'antd';
+
+export const columns = (
+  edit: (b: boolean, v: any) => void,
+  del: (id: any) => void,
+  stateUpdata: (id: any, ck: boolean) => void,
+  mimiAssign: (b: any) => void,
+): ProColumns<any>[] => {
+  return [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      key: 'id',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '小程序appId',
+      dataIndex: 'wechatAppId',
+      key: 'wechatAppId',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+    },
+    {
+      title: '小程序秘钥',
+      dataIndex: 'appSecret',
+      key: 'appSecret',
+      align: 'center',
+      width: 80,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '小程序名称',
+      dataIndex: 'appName',
+      key: 'appName',
+      width: 80,
+      ellipsis: true,
+      align: 'center',
+    },
+    {
+      title: '小程序首页链接',
+      dataIndex: 'homePage',
+      key: 'homePage',
+      width: 80,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+    },
+    {
+      title: '小程序版本号',
+      dataIndex: 'appVersion',
+      key: 'appVersion',
+      width: 80,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+    },
+    {
+      title: '小程序配置信息(json)',
+      dataIndex: 'configParamList',
+      key: 'configParamList',
+      width: 80,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+      render: (a, b) => {
+        return (
+          <Space size={[0, 0]} wrap>
+            {b?.configParamList?.map((item: any, index: number) => {
+              return (
+                <Tooltip title={JSON.stringify(item['additionalProp' + (index + 1)])} key={index}>
+                  <Button type="link" size="small">
+                    配置{index + 1}
+                  </Button>
+                </Tooltip>
+              );
+            })}
+          </Space>
+        );
+      },
+    },
+    {
+      title: '备注',
+      dataIndex: 'remark',
+      key: 'remark',
+      width: 80,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '状态',
+      dataIndex: 'enabled',
+      key: 'enabled',
+      width: 80,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+      render: (a: any, b: any) => {
+        return (
+          <Switch
+            checkedChildren="正常"
+            unCheckedChildren="停用"
+            checked={b?.enabled}
+            onChange={(ck) => {
+              stateUpdata(b.id, ck);
+            }}
+          />
+        );
+      },
+    },
+    {
+      title: '已指派(分销商)',
+      dataIndex: 'distributorInfo',
+      width: 80,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+      render: (a, b: any) => {
+        return <a>{b?.distributorInfo?.companyName}</a>;
+      },
+    },
+    {
+      title: '商户',
+      dataIndex: 'mchName',
+      width: 80,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+      render: (a, b: any) => {
+        return <a>{b?.mchInfo?.mchName}</a>;
+      },
+    },
+    {
+      title: '操作',
+      dataIndex: 'cz',
+      key: 'cz',
+      width: 160,
+      align: 'center',
+      hideInSearch: true,
+      render: (a: any, b: any) => {
+        return (
+          <Space size={[0, 0]}>
+            <Button
+              onClick={() => {
+                edit(true, b);
+              }}
+              size="small"
+              type="link"
+            >
+              编辑
+            </Button>
+            <Popconfirm
+              title={
+                <div>
+                  确定要删除<span style={{ color: 'red' }}>{b.appName}</span>小程序?
+                </div>
+              }
+              onConfirm={() => {
+                del(b.id);
+              }}
+            >
+              <Button size="small" danger type="link">
+                删除
+              </Button>
+            </Popconfirm>
+            <Button
+              onClick={() => {
+                mimiAssign(b);
+              }}
+              size="small"
+              type="link"
+            >
+              指派
+            </Button>
+          </Space>
+        );
+      },
+    },
+  ];
+};

+ 46 - 4
src/pages/distribution/orderLog/long/index.tsx

@@ -1,4 +1,46 @@
-function Page(){
-    return "长篇订单"
-}
-export default Page
+import { useAjax } from '@/Hook/useAjax';
+import { apiLongBookOrderListOfPage } from '@/services/distribution/orderLog';
+import { ActionType, PageContainer, ProTable } from '@ant-design/pro-components';
+import { useRef } from 'react';
+import { columns } from './tableConfig';
+
+type DataItem = {
+  name: string;
+  state: string;
+};
+const Page: React.FC = () => {
+  const actionRef = useRef<ActionType>();
+  let LongBookOrderListOfPage = useAjax((params) => apiLongBookOrderListOfPage(params), {
+    type: 'table',
+  }); //订单
+
+  return (
+    <PageContainer>
+      <ProTable<any, any>
+        headerTitle={'长篇小说订单记录列表'}
+        actionRef={actionRef}
+        rowKey={(r) => r.id}
+        search={{
+          labelWidth: 120,
+        }}
+        request={async (params) => {
+          if (params.orderTime) {
+            params.orderTimeBegin = params.orderTime[0];
+            params.orderTimeEnd = params.orderTime[1];
+          }
+          if (params.amount) {
+            params.amountMin = params.amount[0];
+            params.amountMax = params.amount[1];
+          }
+          delete params.orderTime;
+          delete params.amount;
+          return await LongBookOrderListOfPage.run(params);
+        }}
+        columns={columns()}
+        scroll={{ x: 'auto' }}
+        // bordered
+      />
+    </PageContainer>
+  );
+};
+export default Page;

+ 148 - 0
src/pages/distribution/orderLog/long/tableConfig.tsx

@@ -0,0 +1,148 @@
+import { ProColumns } from '@ant-design/pro-components';
+
+export const columns = (): ProColumns<any>[] => {
+  return [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      key: 'id',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: 'userId',
+      dataIndex: 'userId',
+      key: 'userId',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+    },
+    {
+      title: '小说名称',
+      dataIndex: 'bookName',
+      key: 'bookName',
+      align: 'center',
+      width: 150,
+      ellipsis: true,
+      render: (a, b) => {
+        return b?.bookInfo?.bookName;
+      },
+    },
+    {
+      title: '应用名称',
+      dataIndex: 'appName',
+      key: 'appName',
+      width: 120,
+      align: 'center',
+      ellipsis: true,
+      render: (a, b) => {
+        return b?.appInfo?.appName;
+      },
+    },
+    {
+      title: '分销商名称',
+      dataIndex: 'companyName',
+      key: 'companyName',
+      width: 120,
+      align: 'center',
+      ellipsis: true,
+      render: (a, b) => {
+        return b?.distributorInfo?.companyName;
+      },
+    },
+    {
+      title: '订单号',
+      dataIndex: 'orderId',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+    },
+    {
+      title: '商户订单号',
+      dataIndex: 'merchantOrderId',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+    },
+    {
+      title: '订单类型',
+      dataIndex: 'orderType',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+    },
+    {
+      title: '充值金额',
+      dataIndex: 'amount',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '实际支付金额',
+      dataIndex: 'payAmount',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '支付渠道',
+      dataIndex: 'payChannel',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '支付状态',
+      dataIndex: 'orderStatus',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+    },
+    {
+      title: '支付场景',
+      dataIndex: 'orderCondition',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+    },
+    {
+      title: '下单时间',
+      dataIndex: 'orderTime',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '支付时间',
+      dataIndex: 'payTime',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    // 搜索
+    {
+      title: '支付金额',
+      dataIndex: 'amount',
+      valueType: 'digitRange',
+      width: 170,
+      hideInTable: true,
+    },
+    {
+      title: '下单时间',
+      dataIndex: 'orderTime',
+      valueType: 'dateRange',
+      hideInTable: true,
+      fieldProps: {
+        style: { width: 250 },
+      },
+    },
+  ];
+};

+ 46 - 4
src/pages/distribution/orderLog/short/index.tsx

@@ -1,4 +1,46 @@
-function Page(){
-    return "短篇订单"
-}
-export default Page
+import { useAjax } from '@/Hook/useAjax';
+import { apiShortBookOrderListOfPage } from '@/services/distribution/orderLog';
+import { ActionType, PageContainer, ProTable } from '@ant-design/pro-components';
+import { useRef } from 'react';
+import { columns } from './tableConfig';
+
+type DataItem = {
+  name: string;
+  state: string;
+};
+const Page: React.FC = () => {
+  const actionRef = useRef<ActionType>();
+  let ShortBookOrderListOfPage = useAjax((params) => apiShortBookOrderListOfPage(params), {
+    type: 'table',
+  }); //订单
+
+  return (
+    <PageContainer>
+      <ProTable<any, any>
+        headerTitle={'短篇小说订单记录列表'}
+        actionRef={actionRef}
+        rowKey={(r) => r.id}
+        search={{
+          labelWidth: 120,
+        }}
+        request={async (params) => {
+          if (params.orderTime) {
+            params.orderTimeBegin = params.orderTime[0];
+            params.orderTimeEnd = params.orderTime[1];
+          }
+          if (params.amount) {
+            params.amountMin = params.amount[0];
+            params.amountMax = params.amount[1];
+          }
+          delete params.orderTime;
+          delete params.amount;
+          return await ShortBookOrderListOfPage.run(params);
+        }}
+        columns={columns()}
+        scroll={{ x: 'auto' }}
+        // bordered
+      />
+    </PageContainer>
+  );
+};
+export default Page;

+ 148 - 0
src/pages/distribution/orderLog/short/tableConfig.tsx

@@ -0,0 +1,148 @@
+import { ProColumns } from '@ant-design/pro-components';
+
+export const columns = (): ProColumns<any>[] => {
+  return [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      key: 'id',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: 'userId',
+      dataIndex: 'userId',
+      key: 'userId',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+    },
+    {
+      title: '小说名称',
+      dataIndex: 'bookName',
+      key: 'bookName',
+      align: 'center',
+      width: 150,
+      ellipsis: true,
+      render: (a, b) => {
+        return b?.bookInfo?.bookName;
+      },
+    },
+    {
+      title: '应用名称',
+      dataIndex: 'appName',
+      key: 'appName',
+      width: 120,
+      align: 'center',
+      ellipsis: true,
+      render: (a, b) => {
+        return b?.appInfo?.appName;
+      },
+    },
+    {
+      title: '分销商名称',
+      dataIndex: 'companyName',
+      key: 'companyName',
+      width: 120,
+      align: 'center',
+      ellipsis: true,
+      render: (a, b) => {
+        return b?.distributorInfo?.companyName;
+      },
+    },
+    {
+      title: '订单号',
+      dataIndex: 'orderId',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+    },
+    {
+      title: '商户订单号',
+      dataIndex: 'merchantOrderId',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+    },
+    {
+      title: '订单类型',
+      dataIndex: 'orderType',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+    },
+    {
+      title: '充值金额',
+      dataIndex: 'amount',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '实际支付金额',
+      dataIndex: 'payAmount',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '支付渠道',
+      dataIndex: 'payChannel',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '支付状态',
+      dataIndex: 'orderStatus',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+    },
+    {
+      title: '支付场景',
+      dataIndex: 'orderCondition',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+    },
+    {
+      title: '下单时间',
+      dataIndex: 'orderTime',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '支付时间',
+      dataIndex: 'payTime',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    // 搜索
+    {
+      title: '支付金额',
+      dataIndex: 'amount',
+      valueType: 'digitRange',
+      width: 170,
+      hideInTable: true,
+    },
+    {
+      title: '下单时间',
+      dataIndex: 'orderTime',
+      valueType: 'dateRange',
+      hideInTable: true,
+      fieldProps: {
+        style: { width: 250 },
+      },
+    },
+  ];
+};

+ 41 - 4
src/pages/distribution/payLog/long/index.tsx

@@ -1,4 +1,41 @@
-function Page(){
-    return "长篇消费"
-}
-export default Page
+import { useAjax } from '@/Hook/useAjax';
+import { apiLongBookCoinChangeLogOfPage } from '@/services/distribution/payLog';
+import { ActionType, PageContainer, ProTable } from '@ant-design/pro-components';
+import { useRef } from 'react';
+import { columns } from './tableConfig';
+
+type DataItem = {
+  name: string;
+  state: string;
+};
+const Page: React.FC = () => {
+  const actionRef = useRef<ActionType>();
+  let LongBookCoinChangeLogOfPage = useAjax((params) => apiLongBookCoinChangeLogOfPage(params), {
+    type: 'table',
+  }); //订单
+
+  return (
+    <PageContainer>
+      <ProTable<any, any>
+        headerTitle={'长篇小说书币变更列表'}
+        actionRef={actionRef}
+        rowKey={(r) => r.id}
+        search={{
+          labelWidth: 120,
+        }}
+        request={async (params) => {
+          if (params.time) {
+            params.startTime = params.time[0];
+            params.endTime = params.time[1];
+          }
+          delete params.time;
+          return await LongBookCoinChangeLogOfPage.run(params);
+        }}
+        columns={columns()}
+        scroll={{ x: 'auto' }}
+        // bordered
+      />
+    </PageContainer>
+  );
+};
+export default Page;

+ 108 - 0
src/pages/distribution/payLog/long/tableConfig.tsx

@@ -0,0 +1,108 @@
+import { ProColumns } from '@ant-design/pro-components';
+
+export const columns = (): ProColumns<any>[] => {
+  return [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      key: 'id',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: 'userId',
+      dataIndex: 'userId',
+      key: 'userId',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+    },
+    {
+      title: '小说名称',
+      dataIndex: 'bookName',
+      key: 'bookName',
+      align: 'center',
+      width: 150,
+      ellipsis: true,
+      render: (a, b) => {
+        return b?.bookInfo?.bookName;
+      },
+    },
+    {
+      title: '章节名称',
+      dataIndex: 'chapterId',
+      align: 'center',
+      width: 150,
+      ellipsis: true,
+      hideInSearch: true,
+      render: (a, b) => {
+        return b?.chapterInfo?.chapterName;
+      },
+    },
+    {
+      title: '应用名称',
+      dataIndex: 'appName',
+      key: 'appName',
+      width: 120,
+      align: 'center',
+      ellipsis: true,
+      render: (a, b) => {
+        return b?.appInfo?.appName;
+      },
+    },
+    {
+      title: '分销商名称',
+      dataIndex: 'companyName',
+      key: 'companyName',
+      width: 120,
+      align: 'center',
+      ellipsis: true,
+      render: (a, b) => {
+        return b?.distributorInfo?.companyName;
+      },
+    },
+    {
+      title: '变更类型',
+      dataIndex: 'changeType',
+      width: 120,
+      align: 'center',
+      ellipsis: true,
+    },
+    {
+      title: '变更数量',
+      dataIndex: 'coinChangeNum',
+      width: 120,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '剩余书币',
+      dataIndex: 'coinRemainingNum',
+      width: 120,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '变更时间',
+      dataIndex: 'createTime',
+      width: 120,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    //搜索
+    {
+      title: '变更时间',
+      dataIndex: 'time',
+      valueType: 'dateRange',
+      fieldProps: {
+        style: { width: 250 },
+      },
+      hideInTable: true,
+    },
+  ];
+};

+ 41 - 4
src/pages/distribution/payLog/short/index.tsx

@@ -1,4 +1,41 @@
-function Page(){
-    return "短篇消费"
-}
-export default Page
+import { useAjax } from '@/Hook/useAjax';
+import { apiShortBookCoinChangeLogOfPage } from '@/services/distribution/payLog';
+import { ActionType, PageContainer, ProTable } from '@ant-design/pro-components';
+import { useRef } from 'react';
+import { columns } from './tableConfig';
+
+type DataItem = {
+  name: string;
+  state: string;
+};
+const Page: React.FC = () => {
+  const actionRef = useRef<ActionType>();
+  let ShortBookCoinChangeLogOfPage = useAjax((params) => apiShortBookCoinChangeLogOfPage(params), {
+    type: 'table',
+  }); //订单
+
+  return (
+    <PageContainer>
+      <ProTable<any, any>
+        headerTitle={'短篇小说书币变更列表'}
+        actionRef={actionRef}
+        rowKey={(r) => r.id}
+        search={{
+          labelWidth: 120,
+        }}
+        request={async (params) => {
+          if (params.time) {
+            params.startTime = params.time[0];
+            params.endTime = params.time[1];
+          }
+          delete params.time;
+          return await ShortBookCoinChangeLogOfPage.run(params);
+        }}
+        columns={columns()}
+        scroll={{ x: 'auto' }}
+        // bordered
+      />
+    </PageContainer>
+  );
+};
+export default Page;

+ 105 - 0
src/pages/distribution/payLog/short/tableConfig.tsx

@@ -0,0 +1,105 @@
+import { ProColumns } from '@ant-design/pro-components';
+
+export const columns = (): ProColumns<any>[] => {
+  return [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      key: 'id',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: 'userId',
+      dataIndex: 'userId',
+      key: 'userId',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+    },
+    {
+      title: '小说名称',
+      dataIndex: 'bookName',
+      key: 'bookName',
+      align: 'center',
+      width: 150,
+      ellipsis: true,
+      render: (a, b) => {
+        return b?.bookInfo?.bookName;
+      },
+    },
+    {
+      title: '短篇小说-段落',
+      dataIndex: 'paragraphId',
+      align: 'center',
+      width: 150,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '应用名称',
+      dataIndex: 'appName',
+      key: 'appName',
+      width: 120,
+      align: 'center',
+      ellipsis: true,
+      render: (a, b) => {
+        return b?.appInfo?.appName;
+      },
+    },
+    {
+      title: '分销商名称',
+      dataIndex: 'companyName',
+      key: 'companyName',
+      width: 120,
+      align: 'center',
+      ellipsis: true,
+      render: (a, b) => {
+        return b?.distributorInfo?.companyName;
+      },
+    },
+    {
+      title: '变更类型',
+      dataIndex: 'changeType',
+      width: 120,
+      align: 'center',
+      ellipsis: true,
+    },
+    {
+      title: '变更数量',
+      dataIndex: 'coinChangeNum',
+      width: 120,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '剩余书币',
+      dataIndex: 'coinRemainingNum',
+      width: 120,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '变更时间',
+      dataIndex: 'createTime',
+      width: 120,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    //搜索
+    {
+      title: '变更时间',
+      dataIndex: 'time',
+      valueType: 'dateRange',
+      fieldProps: {
+        style: { width: 250 },
+      },
+      hideInTable: true,
+    },
+  ];
+};

+ 38 - 4
src/pages/distribution/readLog/long/index.tsx

@@ -1,4 +1,38 @@
-function Page(){
-    return "长篇阅读"
-}
-export default Page
+import { useAjax } from '@/Hook/useAjax';
+import { apiLongBookReadLogListOfPage } from '@/services/distribution/readLog';
+import { ActionType, PageContainer, ProTable } from '@ant-design/pro-components';
+import { useRef } from 'react';
+import { columns } from './tableConfig';
+
+const Page: React.FC = () => {
+  const actionRef = useRef<ActionType>();
+  let LongBookReadLogListOfPage = useAjax((params) => apiLongBookReadLogListOfPage(params), {
+    type: 'table',
+  }); //阅读记录
+
+  return (
+    <PageContainer>
+      <ProTable<any, any>
+        headerTitle={'长篇小说阅读记录列表'}
+        actionRef={actionRef}
+        rowKey={(r) => r.id}
+        search={{
+          labelWidth: 120,
+        }}
+        request={async (params) => {
+          console.log('params', params);
+          if (params.createTime) {
+            params.startTime = params.createTime[0];
+            params.endTime = params.createTime[1];
+          }
+          delete params.createTime;
+          return await LongBookReadLogListOfPage.run(params);
+        }}
+        columns={columns()}
+        scroll={{ x: 'auto' }}
+        // bordered
+      />
+    </PageContainer>
+  );
+};
+export default Page;

+ 90 - 0
src/pages/distribution/readLog/long/tableConfig.tsx

@@ -0,0 +1,90 @@
+import { ProColumns } from '@ant-design/pro-components';
+
+export const columns = (): ProColumns<any>[] => {
+  return [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      key: 'id',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: 'userId',
+      dataIndex: 'userId',
+      key: 'userId',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+    },
+    {
+      title: '小说名称',
+      dataIndex: 'bookName',
+      key: 'bookName',
+      align: 'center',
+      width: 150,
+      ellipsis: true,
+      render: (a, b) => {
+        return b?.bookInfo?.bookName;
+      },
+    },
+    {
+      title: '小说章节名称',
+      dataIndex: 'chapterName',
+      key: 'chapterName',
+      width: 80,
+      ellipsis: true,
+      align: 'center',
+      render: (a, b) => {
+        return b?.bookChapterInfo?.chapterName;
+      },
+    },
+    {
+      title: '微信小程序名称',
+      dataIndex: 'appName',
+      key: 'appName',
+      width: 120,
+      align: 'center',
+      ellipsis: true,
+      render: (a, b) => {
+        return b?.appInfo?.appName;
+      },
+    },
+    {
+      title: '分销商名称',
+      dataIndex: 'companyName',
+      key: 'companyName',
+      width: 120,
+      align: 'center',
+      ellipsis: true,
+      render: (a, b) => {
+        return b?.distributorInfo?.companyName;
+      },
+    },
+    {
+      title: '阅读时间',
+      dataIndex: 'createTime',
+      key: 'createTime',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    //
+    {
+      title: '阅读时间',
+      dataIndex: 'createTime',
+      key: 'createTime',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+      valueType: 'dateRange',
+      fieldProps: {
+        style: { width: 250 },
+      },
+      hideInTable: true,
+    },
+  ];
+};

+ 38 - 4
src/pages/distribution/readLog/short/index.tsx

@@ -1,4 +1,38 @@
-function Page(){
-    return "短篇阅读"
-}
-export default Page
+import { useAjax } from '@/Hook/useAjax';
+import { apiShortBookReadLogListOfPage } from '@/services/distribution/readLog';
+import { ActionType, PageContainer, ProTable } from '@ant-design/pro-components';
+import { useRef } from 'react';
+import { columns } from './tableConfig';
+
+const Page: React.FC = () => {
+  const actionRef = useRef<ActionType>();
+  let ShortBookReadLogListOfPage = useAjax((params) => apiShortBookReadLogListOfPage(params), {
+    type: 'table',
+  }); //阅读记录
+
+  return (
+    <PageContainer>
+      <ProTable<any, any>
+        headerTitle={'短篇小说阅读记录列表'}
+        actionRef={actionRef}
+        rowKey={(r) => r.id}
+        search={{
+          labelWidth: 120,
+        }}
+        request={async (params) => {
+          console.log('params', params);
+          if (params.createTime) {
+            params.startTime = params.createTime[0];
+            params.endTime = params.createTime[1];
+          }
+          delete params.createTime;
+          return await ShortBookReadLogListOfPage.run(params);
+        }}
+        columns={columns()}
+        scroll={{ x: 'auto' }}
+        // bordered
+      />
+    </PageContainer>
+  );
+};
+export default Page;

+ 88 - 0
src/pages/distribution/readLog/short/tableConfig.tsx

@@ -0,0 +1,88 @@
+import { ProColumns } from '@ant-design/pro-components';
+
+export const columns = (): ProColumns<any>[] => {
+  return [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      key: 'id',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: 'userId',
+      dataIndex: 'userId',
+      key: 'userId',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+    },
+    {
+      title: '小说名称',
+      dataIndex: 'bookName',
+      key: 'bookName',
+      align: 'center',
+      width: 150,
+      ellipsis: true,
+      render: (a, b) => {
+        return b?.bookInfo?.bookName;
+      },
+    },
+    {
+      title: '小说段落ID',
+      dataIndex: 'bookParagraphId',
+      key: 'bookParagraphId',
+      width: 80,
+      ellipsis: true,
+      align: 'center',
+      hideInSearch: true,
+    },
+    {
+      title: '微信小程序名称',
+      dataIndex: 'appName',
+      key: 'appName',
+      width: 120,
+      align: 'center',
+      ellipsis: true,
+      render: (a, b) => {
+        return b?.appInfo?.appName;
+      },
+    },
+    {
+      title: '分销商名称',
+      dataIndex: 'companyName',
+      key: 'companyName',
+      width: 120,
+      align: 'center',
+      ellipsis: true,
+      render: (a, b) => {
+        return b?.distributorInfo?.companyName;
+      },
+    },
+    {
+      title: '阅读时间',
+      dataIndex: 'createTime',
+      key: 'createTime',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    //
+    {
+      title: '阅读时间',
+      dataIndex: 'createTime',
+      key: 'createTime',
+      width: 170,
+      align: 'center',
+      ellipsis: true,
+      valueType: 'dateRange',
+      fieldProps: {
+        style: { width: 250 },
+      },
+      hideInTable: true,
+    },
+  ];
+};

+ 107 - 4
src/pages/distribution/system/index.tsx

@@ -1,4 +1,107 @@
-function Page(){
-    return "系统信息"
-}
-export default Page
+import { useAjax } from '@/Hook/useAjax';
+import { apiSysConfigParamsList, apiSysConfigParamsUpdate } from '@/services/distribution/system';
+import {
+  ActionType,
+  BetaSchemaForm,
+  PageContainer,
+  ProFormInstance,
+  ProTable,
+} from '@ant-design/pro-components';
+import { message } from 'antd';
+import { useRef, useState } from 'react';
+import { columns } from './tableConfig';
+
+type DataItem = {
+  name: string;
+  state: string;
+};
+const Page: React.FC = () => {
+  const formRef = useRef<ProFormInstance>();
+  const actionRef = useRef<ActionType>();
+  let [open, setOpen] = useState<any>(null); //新增作者
+  let [editValues, setEditValues] = useState<any>({});
+  let SysConfigParamsList = useAjax(() => apiSysConfigParamsList(), { type: 'noPage' });
+  let SysConfigParamsUpdate = useAjax((params) => apiSysConfigParamsUpdate(params));
+
+  // 编辑
+  const edit = (item: any) => {
+    setEditValues(item);
+    setTimeout(() => {
+      formRef?.current?.setFieldsValue({ ...item });
+    }, 100);
+    setOpen(true);
+  };
+  // 新增 or 编辑
+  const submit = async (values: any) => {
+    if (editValues?.id) {
+      values.id = editValues?.id;
+    }
+    SysConfigParamsUpdate.run(values).then((res) => {
+      if (res.code === 200) {
+        actionRef?.current?.reload();
+        message.success('操作成功!');
+        closeForm(false);
+      }
+    });
+    console.log(values);
+  };
+  // 关闭表单弹窗和重置表单内容
+  const closeForm = (b: boolean, values?: any) => {
+    if (!b) {
+      setEditValues({});
+      formRef?.current?.resetFields?.();
+      setOpen(b);
+    } else {
+    }
+  };
+  return (
+    <PageContainer>
+      <ProTable<any, any>
+        headerTitle={'作者列表'}
+        actionRef={actionRef}
+        rowKey={(r) => r.id}
+        search={false}
+        request={async (params) => {
+          return await SysConfigParamsList.run(params);
+        }}
+        columns={columns(edit)}
+        // bordered
+      />
+      <BetaSchemaForm<DataItem>
+        title={'编辑'}
+        formRef={formRef}
+        width={600}
+        open={open}
+        onOpenChange={(b) => {
+          !b && closeForm(b);
+        }}
+        layoutType={'ModalForm'}
+        labelCol={{ span: 6 }}
+        wrapperCol={{ span: 14 }}
+        layout="horizontal"
+        onFinish={submit}
+        columns={[
+          {
+            title: '配置值',
+            dataIndex: 'configValue',
+            formItemProps: {
+              rules: [
+                {
+                  required: true,
+                  message: '此项为必填项',
+                },
+              ],
+            },
+          },
+          {
+            title: '备注',
+            dataIndex: 'remark',
+            valueType: 'textarea',
+          },
+        ]}
+        loading={SysConfigParamsUpdate?.loading}
+      />
+    </PageContainer>
+  );
+};
+export default Page;

+ 67 - 0
src/pages/distribution/system/tableConfig.tsx

@@ -0,0 +1,67 @@
+import { ProColumns } from '@ant-design/pro-components';
+import { Button, Space } from 'antd';
+
+export const columns = (edit: (params: any) => void): ProColumns<any>[] => {
+  return [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      key: 'id',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '配置标识',
+      dataIndex: 'configKey',
+      key: 'configKey',
+      align: 'center',
+      width: 70,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '配置值',
+      dataIndex: 'configValue',
+      key: 'configValue',
+      align: 'center',
+      width: 80,
+      ellipsis: true,
+      hideInSearch: true,
+    },
+
+    {
+      title: '备注',
+      dataIndex: 'remark',
+      key: 'remark',
+      width: 300,
+      align: 'center',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '操作',
+      dataIndex: 'cz',
+      key: 'cz',
+      width: 160,
+      align: 'center',
+      className: 'table_fixed',
+      hideInSearch: true,
+      render: (a: any, b: any) => {
+        return (
+          <Space>
+            <Button
+              onClick={() => {
+                edit(b);
+              }}
+              type="link"
+            >
+              编辑
+            </Button>
+          </Space>
+        );
+      },
+    },
+  ];
+};

+ 5 - 6
src/requestErrorConfig.ts

@@ -91,9 +91,9 @@ export const errorConfig: RequestConfig = {
       // 拦截请求配置,进行个性化处理。
       // const url = config?.url?.concat('?token = 123');
       // 是否需要设置 token
-      const isAdminToken = (config.headers || {})["authorization"]
+      const isAdminToken = (config.headers || {})['authorization'];
       if (localStorage.getItem('Admin-Token') && !isAdminToken && config?.headers) {
-        config.headers['authorization'] = localStorage.getItem('Admin-Token') + "" // 让每个请求携带自定义token 请根据实际
+        config.headers['authorization'] = localStorage.getItem('Admin-Token') + ''; // 让每个请求携带自定义token 请根据实际
       }
       return { ...config };
     },
@@ -104,10 +104,9 @@ export const errorConfig: RequestConfig = {
     (response) => {
       // 拦截响应数据,进行个性化处理
       const { data } = response as unknown as ResponseStructure;
-      console.log(data)
-      if(data?.code === 500){
-        if(data?.msg==="未登录或登录超时!"){
-          
+      console.log(data);
+      if (data?.code === 500) {
+        if (data?.msg === '未登录或登录超时!') {
         }
       }
       if (data?.success === false) {

+ 0 - 94
src/services/ant-design-pro/api.ts

@@ -1,94 +0,0 @@
-// @ts-ignore
-/* eslint-disable */
-import { request } from '@umijs/max';
-
-/** 获取当前的用户 GET /api/currentUser */
-export async function currentUser(options?: { [key: string]: any }) {
-  return request<{
-    data: API.CurrentUser;
-  }>('/api/currentUser', {
-    method: 'GET',
-    ...(options || {}),
-  });
-}
-
-/** 退出登录接口 POST /api/login/outLogin */
-export async function outLogin(options?: { [key: string]: any }) {
-  return request<Record<string, any>>('/api/login/outLogin', {
-    method: 'POST',
-    ...(options || {}),
-  });
-}
-
-/** 登录接口 POST /api/login/account */
-export async function login(body: API.LoginParams, options?: { [key: string]: any }) {
-  return request<API.LoginResult>('/api/login/account', {
-    method: 'POST',
-    headers: {
-      'Content-Type': 'application/json',
-    },
-    data: body,
-    ...(options || {}),
-  });
-}
-
-/** 此处后端没有提供注释 GET /api/notices */
-export async function getNotices(options?: { [key: string]: any }) {
-  return request<API.NoticeIconList>('/api/notices', {
-    method: 'GET',
-    ...(options || {}),
-  });
-}
-
-/** 获取规则列表 GET /api/rule */
-export async function rule(
-  params: {
-    // query
-    /** 当前的页码 */
-    current?: number;
-    /** 页面的容量 */
-    pageSize?: number;
-  },
-  options?: { [key: string]: any },
-) {
-  return request<API.RuleList>('/api/rule', {
-    method: 'GET',
-    params: {
-      ...params,
-    },
-    ...(options || {}),
-  });
-}
-
-/** 更新规则 PUT /api/rule */
-export async function updateRule(options?: { [key: string]: any }) {
-  return request<API.RuleListItem>('/api/rule', {
-    method: 'POST',
-    data:{
-      method: 'update',
-      ...(options || {}),
-    }
-  });
-}
-
-/** 新建规则 POST /api/rule */
-export async function addRule(options?: { [key: string]: any }) {
-  return request<API.RuleListItem>('/api/rule', {
-    method: 'POST',
-    data:{
-      method: 'post',
-      ...(options || {}),
-    }
-  });
-}
-
-/** 删除规则 DELETE /api/rule */
-export async function removeRule(options?: { [key: string]: any }) {
-  return request<Record<string, any>>('/api/rule', {
-    method: 'POST',
-    data:{
-      method: 'delete',
-      ...(options || {}),
-    }
-  });
-}

+ 0 - 10
src/services/ant-design-pro/index.ts

@@ -1,10 +0,0 @@
-// @ts-ignore
-/* eslint-disable */
-// API 更新时间:
-// API 唯一标识:
-import * as api from './api';
-import * as login from './login';
-export default {
-  api,
-  login,
-};

+ 0 - 21
src/services/ant-design-pro/login.ts

@@ -1,21 +0,0 @@
-// @ts-ignore
-/* eslint-disable */
-import { request } from '@umijs/max';
-
-/** 发送验证码 POST /api/login/captcha */
-export async function getFakeCaptcha(
-  params: {
-    // query
-    /** 手机号 */
-    phone?: string;
-  },
-  options?: { [key: string]: any },
-) {
-  return request<API.FakeCaptcha>('/api/login/captcha', {
-    method: 'GET',
-    params: {
-      ...params,
-    },
-    ...(options || {}),
-  });
-}

+ 0 - 101
src/services/ant-design-pro/typings.d.ts

@@ -1,101 +0,0 @@
-// @ts-ignore
-/* eslint-disable */
-
-declare namespace API {
-  type CurrentUser = {
-    name?: string;
-    avatar?: string;
-    userid?: string;
-    email?: string;
-    signature?: string;
-    title?: string;
-    group?: string;
-    tags?: { key?: string; label?: string }[];
-    notifyCount?: number;
-    unreadCount?: number;
-    country?: string;
-    access?: string;
-    geographic?: {
-      province?: { label?: string; key?: string };
-      city?: { label?: string; key?: string };
-    };
-    address?: string;
-    phone?: string;
-  };
-
-  type LoginResult = {
-    status?: string;
-    type?: string;
-    currentAuthority?: string;
-  };
-
-  type PageParams = {
-    current?: number;
-    pageSize?: number;
-  };
-
-  type RuleListItem = {
-    key?: number;
-    disabled?: boolean;
-    href?: string;
-    avatar?: string;
-    name?: string;
-    owner?: string;
-    desc?: string;
-    callNo?: number;
-    status?: number;
-    updatedAt?: string;
-    createdAt?: string;
-    progress?: number;
-  };
-
-  type RuleList = {
-    data?: RuleListItem[];
-    /** 列表的内容总数 */
-    total?: number;
-    success?: boolean;
-  };
-
-  type FakeCaptcha = {
-    code?: number;
-    status?: string;
-  };
-
-  type LoginParams = {
-    username?: string;
-    password?: string;
-    autoLogin?: boolean;
-    type?: string;
-  };
-
-  type ErrorResponse = {
-    /** 业务约定的错误码 */
-    errorCode: string;
-    /** 业务上的错误信息 */
-    errorMessage?: string;
-    /** 业务上的请求是否成功 */
-    success?: boolean;
-  };
-
-  type NoticeIconList = {
-    data?: NoticeIconItem[];
-    /** 列表的内容总数 */
-    total?: number;
-    success?: boolean;
-  };
-
-  type NoticeIconItemType = 'notification' | 'message' | 'event';
-
-  type NoticeIconItem = {
-    id?: string;
-    extra?: string;
-    key?: string;
-    read?: boolean;
-    avatar?: string;
-    title?: string;
-    status?: string;
-    datetime?: string;
-    description?: string;
-    type?: NoticeIconItemType;
-  };
-}

+ 2 - 1
src/services/api.ts

@@ -1 +1,2 @@
-export let api: any = process.env.NODE_ENV === 'development' ? '/api' : 'https://api.zanxiangwl.com'
+export let api: any =
+  process.env.NODE_ENV === 'development' ? '/api' : 'https://testapi.zanxiangwl.com';

+ 58 - 0
src/services/book/author/index.tsx

@@ -0,0 +1,58 @@
+import { api } from '@/services/api';
+import { request } from '@umijs/max';
+
+/**
+ * 作者修改
+ * @param data
+ * @returns
+ */
+export async function apibookAuthorEdit(params: any) {
+  let { id, ...data } = params;
+  return request(api + `/bookContent/admin/bookAuthor/${id}`, {
+    method: 'PUT',
+    data,
+  });
+}
+
+/**
+ * 作者新增
+ * @param data
+ * @returns
+ */
+export async function apibookAuthorAdd(data: any) {
+  return request(api + '/bookContent/admin/bookAuthor', {
+    method: 'POST',
+    data,
+  });
+}
+/**
+ * 作者删除
+ * @param data
+ * @returns
+ */
+export async function apibookAuthorDel(id: any) {
+  return request(api + `/bookContent/admin/bookAuthor/remove/one/${id}`, {
+    method: 'DELETE',
+  });
+}
+/**
+ * 作者列表
+ * @param corpId
+ * @returns
+ */
+export async function apibookAuthorList(params: number) {
+  return request(api + `/bookContent/admin/bookAuthor/list`, {
+    method: 'GET',
+    params,
+  });
+}
+/**
+ * 作者全部列表
+ * @param corpId
+ * @returns
+ */
+export async function apibookAuthorListAll() {
+  return request(api + `/bookContent/admin/bookAuthor/listAll`, {
+    method: 'GET',
+  });
+}

+ 82 - 0
src/services/book/bookTags/index.tsx

@@ -0,0 +1,82 @@
+import { api } from '@/services/api';
+import { request } from '@umijs/max';
+
+/**
+ * 标签修改
+ * @param data
+ * @returns
+ */
+export async function apiBookContentLabelEdit(params: any) {
+  let { id, ...data } = params;
+  return request(api + `/bookContent/bookContent/admin/bookLabel/${id}`, {
+    method: 'PUT',
+    data,
+  });
+}
+/**
+ * 标签热门修改
+ * @param data
+ * @returns
+ */
+export async function apiBookContentLabelHot(params: any) {
+  let { id, ...data } = params;
+  return request(api + `/bookContent/admin/bookLabel/editIsHot/${id}`, {
+    method: 'PUT',
+    data,
+  });
+}
+/**
+ * 标签排序修改
+ * @param data
+ * @returns
+ */
+export async function apiBookContentLabelSort(params: any) {
+  let { id, ...data } = params;
+  return request(api + `/bookContent/admin/bookLabel/editSort/${id}`, {
+    method: 'PUT',
+    data,
+  });
+}
+/**
+ * 标签新增
+ * @param data
+ * @returns
+ */
+export async function apiBookContentLabelAdd(data: any) {
+  return request(api + '/bookContent/admin/bookLabel', {
+    method: 'POST',
+    data,
+  });
+}
+/**
+ * 标签删除
+ * @param data
+ * @returns
+ */
+export async function apiBookContentLabelDel(id: any) {
+  return request(api + `/bookContent/admin/bookLabel/remove/one/${id}`, {
+    method: 'DELETE',
+  });
+}
+/**
+ * 标签列表
+ * @param corpId
+ * @returns
+ */
+export async function apiBookContentLabelList(params: number) {
+  return request(api + `/bookContent/admin/bookLabel/list`, {
+    method: 'GET',
+    params,
+  });
+}
+/**
+ * 标签列表
+ * @param corpId
+ * @returns
+ */
+export async function apiBookContentLabelListAll(params?: any) {
+  return request(api + `/bookContent/admin/bookLabel/listAll`, {
+    method: 'GET',
+    params,
+  });
+}

+ 75 - 0
src/services/book/classifyList/index.tsx

@@ -0,0 +1,75 @@
+import { api } from '@/services/api';
+import { request } from '@umijs/max';
+
+/**
+ * 小说类别修改
+ * @param data
+ * @returns
+ */
+export async function apibookCategoryEdit(params: any) {
+  let { id, ...data } = params;
+  return request(api + `/bookContent/admin/bookCategory/${id}`, {
+    method: 'PUT',
+    data,
+  });
+}
+/**
+ * 小说排序修改
+ * @param data
+ * @returns
+ */
+export async function apibookCategorySort(params: any) {
+  let { id, ...data } = params;
+  return request(api + `/bookContent/admin/bookCategory/editSort/${id}`, {
+    method: 'PUT',
+    data,
+  });
+}
+/**
+ * 小说类别新增
+ * @param data
+ * @returns
+ */
+export async function apibookCategoryAdd(data: any) {
+  return request(api + '/bookContent/admin/bookCategory', {
+    method: 'POST',
+    data,
+  });
+}
+/**
+ * 小说类别单个查询
+ * @param data
+ * @returns
+ */
+/**
+ * 小说类别删除
+ * @param data
+ * @returns
+ */
+export async function apibookCategoryDel(id: any) {
+  return request(api + `/bookContent/admin/bookCategory/remove/one/${id}`, {
+    method: 'DELETE',
+  });
+}
+/**
+ * 小说类别列表
+ * @param corpId
+ * @returns
+ */
+export async function apibookCategoryList(params: number) {
+  return request(api + `/bookContent/admin/bookCategory/list`, {
+    method: 'GET',
+    params,
+  });
+}
+/**
+ * 小说类别列表全部
+ * @param corpId
+ * @returns
+ */
+export async function apibookCategoryListAll(params?: any) {
+  return request(api + `/bookContent/admin/bookCategory/listAll`, {
+    method: 'GET',
+    params,
+  });
+}

+ 179 - 0
src/services/book/longbookList/index.tsx

@@ -0,0 +1,179 @@
+import { api } from '@/services/api';
+import { request } from '@umijs/max';
+
+/**
+ * 小说修改
+ * @param data
+ * @returns
+ */
+export async function apiBookContentLongBookInfoEdit(params: any) {
+  let { id, ...data } = params;
+  return request(api + `/bookContent/admin/longBookInfo/${id}`, {
+    method: 'PUT',
+    data,
+  });
+}
+/**
+ * 小说新增
+ * 
+ * @param   "workDirection" 作品方向;0-男频 1-女频
+  @param "categoryId" 类别ID
+ @param "labelIds" 标签
+ @param "picUrl" 封面地址,多个用,拼接
+ @param "bookName" 小说名
+ @param "authorId" 作者ID
+  @param"bookDesc" 书籍描述
+ @param "source" 来源(UPLOAD:管理员上传、CREATOR:创作者)
+ @param "bookStatus" 书籍状态;0-连载中 1-已完结
+ @param "shelveStatus" 上下架状态;0-上架 1-下架
+@param  "vipFree" vip 是否免费阅读;0-否,1是
+  @param"paymentType" 付费类型:0-免费 1-按千字收费 2-整本收费
+@param  "paymentMoney" 付费金额(分)
+ */
+export async function apiBookContentLongBookInfoAdd(data: any) {
+  return request(api + '/bookContent/admin/longBookInfo', {
+    method: 'POST',
+    data,
+  });
+}
+/**
+ * 小说批量上下架
+ * @param data
+ * @returns
+ */
+export async function apiBookContentLongBookInfoBatch(data: any) {
+  let { shelve, ids } = data;
+  return request(api + `/bookContent/admin/longBookInfo/shelve/batch?shelve=${shelve}`, {
+    method: 'POST',
+    data: ids,
+  });
+}
+/**
+ * 小说删除
+ * @param data
+ * @returns
+ */
+export async function apiBookContentLongBookInfoDel(id: any) {
+  return request(api + `/bookContent/admin/longBookInfo/remove/one/${id}`, {
+    method: 'DELETE',
+  });
+}
+/**
+ * 小说批量删除
+ * @param data
+ * @returns
+ */
+export async function apiBookContentLongBookInfoDels(params: any[]) {
+  return request(api + `/bookContent/admin/longBookInfo/remove/batch`, {
+    method: 'DELETE',
+    data: params,
+  });
+}
+/**
+ * 小说列表
+ * @param workDirection 频道
+ * @param categoryId 分类ID
+ * @param labelId 标签ID
+ * @param bookName 书籍名称
+ * @param authorName 作者名称
+ * @param startWordCount 最少字数
+ * @param endWordCount 最大字数
+ * @param bookStatus 书籍状态;0-连载中 1-已完结
+ * @param shelveStatus 上下架状态;0-上架 1-下架
+ * @param paymentType  付费类型:0-免费 1-按千字收费 2-整本收费
+ * @param vipFree vip 是否免费阅读;0-否 1是
+ * @returns
+ */
+export async function apiBookContentLongBookInfoList(params: any) {
+  return request(api + `/bookContent/admin/longBookInfo/list`, {
+    method: 'GET',
+    params,
+  });
+}
+
+// =================================章节===========================
+/**
+ * 小说章节列表
+ * @param bookId
+ * @param sort
+ * @param isPayment
+ * */
+export async function apiLongBookParagraphList(params: any) {
+  return request(api + `/bookContent/admin/longBookChapter/list`, {
+    method: 'GET',
+    params,
+  });
+}
+/**
+ * 小说章节内容
+ * @param bookId
+ * @param sort
+ * @param isPayment
+ * */
+export async function apiLongBookParagraph(id: any) {
+  return request(api + `/bookContent/admin/longBookChapter/one/${id}`, {
+    method: 'GET',
+  });
+}
+
+/**
+ * 章节新增
+ *
+ * @param   bookId 小说id
+ * @param   paragraphNo 章节号
+ * @param   paragraphNo 小说章节内容
+ * @param   beginPayment 是否是开始收费章节
+ */
+export async function apiLongBookParagraphAdd(data: any) {
+  return request(api + '/bookContent/admin/longBookChapter', {
+    method: 'POST',
+    data,
+  });
+}
+/**
+ * 章节修改
+ * @param id 章节ID
+ * @param   bookId 小说id
+ * @param   paragraphNo 章节号
+ * @param   paragraphNo 小说章节内容
+ * @param   beginPayment 是否是开始收费章节
+ */
+export async function apiLongBookParagraphEdit(params: any) {
+  let { id, ...data } = params;
+  return request(api + `/bookContent/admin/longBookChapter/${id}`, {
+    method: 'PUT',
+    data,
+  });
+}
+
+/**
+ * 章节删除
+ * @param id  章节ID
+ */
+export async function apiLongBookParagraphDel(id: any) {
+  return request(api + `/bookContent/admin/longBookChapter/remove/one/${id}`, {
+    method: 'DELETE',
+  });
+}
+
+/**上传小说
+ * @param bookId
+ * @param file
+ */
+export async function apiLongBookUploadLongBook(data: any) {
+  return request(api + '/bookContent/admin/longBookInfo/uploadLongBook', {
+    method: 'POST',
+    data,
+  });
+}
+/**确认并提交上传的小说信息
+ * @param fileUrl
+ * @param bookId
+ * @param chapterInfos
+ */
+export async function apiLongBookConfirmUploadLongBook(data: any) {
+  return request(api + '/bookContent/admin/longBookInfo/confirmUploadLongBook', {
+    method: 'POST',
+    data,
+  });
+}

+ 183 - 0
src/services/book/shortbookList/index.tsx

@@ -0,0 +1,183 @@
+import { api } from '@/services/api';
+import { request } from '@umijs/max';
+
+/**
+ * 小说修改
+ * @param data
+ * @returns
+ */
+export async function apiBookContentShortBookInfoEdit(params: any) {
+  let { id, ...data } = params;
+  return request(api + `/bookContent/admin/shortBookInfo/${id}`, {
+    method: 'PUT',
+    data,
+  });
+}
+/**
+ * 小说新增
+ * 
+ * @param   "workDirection" 作品方向;0-男频 1-女频
+  @param "categoryId" 类别ID
+ @param "labelIds" 标签
+ @param "picUrl" 封面地址,多个用,拼接
+ @param "bookName" 小说名
+ @param "authorId" 作者ID
+  @param"bookDesc" 书籍描述
+ @param "source" 来源(UPLOAD:管理员上传、CREATOR:创作者)
+ @param "bookStatus" 书籍状态;0-连载中 1-已完结
+ @param "shelveStatus" 上下架状态;0-上架 1-下架
+@param  "vipFree" vip 是否免费阅读;0-否,1是
+  @param"paymentType" 付费类型:0-免费 1-按千字收费 2-整本收费
+@param  "paymentMoney" 付费金额(分)
+ */
+export async function apiBookContentShortBookInfoAdd(data: any) {
+  return request(api + '/bookContent/admin/shortBookInfo', {
+    method: 'POST',
+    data,
+  });
+}
+/**
+ * 小说批量上下架
+ * @param data
+ * @returns
+ */
+export async function apiBookContentShortBookInfoBatch(data: any) {
+  let { shelve, ids } = data;
+  return request(api + `/bookContent/admin/shortBookInfo/shelve/batch?shelve=${shelve}`, {
+    method: 'POST',
+    data: ids,
+  });
+}
+/**
+ * 小说删除
+ * @param data
+ * @returns
+ */
+export async function apiBookContentShortBookInfoDel(id: any) {
+  return request(api + `/bookContent/admin/shortBookInfo/remove/one/${id}`, {
+    method: 'DELETE',
+  });
+}
+/**
+ * 小说批量删除
+ * @param data
+ * @returns
+ */
+export async function apiBookContentShortBookInfoDels(params: any[]) {
+  return request(api + `/bookContent/admin/shortBookInfo/remove/batch`, {
+    method: 'DELETE',
+    data: params,
+  });
+}
+/**
+ * 小说列表
+ * @param workDirection 频道
+ * @param categoryId 分类ID
+ * @param labelId 标签ID
+ * @param bookName 书籍名称
+ * @param authorName 作者名称
+ * @param startWordCount 最少字数
+ * @param endWordCount 最大字数
+ * @param bookStatus 书籍状态;0-连载中 1-已完结
+ * @param shelveStatus 上下架状态;0-上架 1-下架
+ * @param paymentType  付费类型:0-免费 1-按千字收费 2-整本收费
+ * @param vipFree vip 是否免费阅读;0-否 1是
+ * @returns
+ */
+export async function apiBookContentShortBookInfoList(params: number) {
+  return request(api + `/bookContent/admin/shortBookInfo/list`, {
+    method: 'GET',
+    params,
+  });
+}
+
+// =================================段落===========================
+/**
+ * 小说段落列表
+ * @param bookId
+ * @param sort
+ * @param isPayment
+ * */
+export async function apiShortBookParagraphList(params: any) {
+  return request(api + `/bookContent/admin/shortBookParagraph/list`, {
+    method: 'GET',
+    params,
+  });
+}
+/**
+ * 小说段落列表
+ * @param bookId
+ * @param sort
+ * @param isPayment
+ * */
+export async function apiShortBookParagraph(id: any) {
+  return request(api + `/bookContent/admin/shortBookParagraph/one/${id}`, {
+    method: 'GET',
+  });
+}
+/**
+ * 段落新增
+ *
+ * @param   bookId 小说id
+ * @param   paragraphNo 段落号
+ * @param   paragraphNo 小说段落内容
+ * @param   beginPayment 是否是开始收费段落
+ */
+export async function apiShortBookParagraphAdd(data: any) {
+  return request(api + '/bookContent/admin/shortBookParagraph', {
+    method: 'POST',
+    data,
+  });
+}
+/**
+ * 段落修改
+ * @param id 段落ID
+ * @param   bookId 小说id
+ * @param   paragraphNo 段落号
+ * @param   paragraphNo 小说段落内容
+ * @param   beginPayment 是否是开始收费段落
+ */
+export async function apiShortBookParagraphEdit(params: any) {
+  let { id, ...data } = params;
+  return request(api + `/bookContent/admin/shortBookParagraph/${id}`, {
+    method: 'PUT',
+    data,
+  });
+}
+
+/**
+ * 段落删除
+ * @param id  段落ID
+ */
+export async function apiShortBookParagraphDel(id: any) {
+  return request(api + `/bookContent/admin/shortBookParagraph/remove/one/${id}`, {
+    method: 'DELETE',
+  });
+}
+
+/**
+ * 上传段落
+ *
+ * @param   bookId 小说id
+ * @param   file
+ */
+export async function apiShortBookUploadShortBook(data: any) {
+  return request(api + '/bookContent/admin/shortBookInfo/uploadShortBook', {
+    method: 'POST',
+    data,
+  });
+}
+
+/**
+ * 获取短篇小说内容
+ *
+ * @param   bookId 小说id
+ * @param   pageNum
+ * @param   pageSize
+ */
+export async function apiShortBookGetInfo(params: any) {
+  return request(api + '/bookContent/admin/shortBookInfo/getInfo', {
+    method: 'GET',
+    params,
+  });
+}

+ 92 - 0
src/services/distribution/accountInfo/index.tsx

@@ -0,0 +1,92 @@
+import { api } from '@/services/api';
+import { request } from '@umijs/max';
+/**
+ * 分销商账号状态更新
+ * @param id
+ */
+export async function apiDistributorAccountUpdateEnabled(params: any) {
+  let { id, ...data } = params;
+  return request(api + `/bookDistributionPlatform/admin/distributorAccount/updateEnabled/${id}`, {
+    method: 'PUT',
+    data,
+  });
+}
+/**
+ * 分销商账号更新
+ * @param id
+ * @param distributorId   分销商 id
+ * @param nickname   账号昵称
+ * @param password   账号密码
+ * @param phoneNum   手机号
+ * @param subAccountList   子账号列表
+ * @param remark   备注
+ */
+export async function apiDistributorAccountUpdate(params: any) {
+  let { id, ...data } = params;
+  return request(api + `/bookDistributionPlatform/admin/distributorAccount/update/${id}`, {
+    method: 'PUT',
+    data,
+  });
+}
+
+/**
+ * 分销商账号新增
+ * @param distributorId   分销商 id
+ * @param nickname   账号昵称
+ * @param password   账号密码
+ * @param phoneNum   手机号
+ * @param subAccountList   子账号列表
+ * @param remark   备注
+ */
+export async function apiDistributorAccount(data: any) {
+  return request(api + `/bookDistributionPlatform/admin/distributorAccount`, {
+    method: 'POST',
+    data,
+  });
+}
+
+/**
+ * 分销商账号分页列表
+ * @param pageNum
+ * @param pageSize
+ * @param nickname   账号昵称
+ * @param phoneNum   手机
+ */
+export async function apiDistributorAccountPageList(params: any) {
+  console.log(params);
+  return request(api + `/bookDistributionPlatform/admin/distributorAccount/pageList`, {
+    method: 'GET',
+    params,
+  });
+}
+/**
+ * 分销商账号列表
+ */
+export async function apiDistributorAccountList(distributorId: any) {
+  return request(
+    api + `/bookDistributionPlatform/admin/distributorAccount/list?distributorId=${distributorId}`,
+    {
+      method: 'GET',
+    },
+  );
+}
+
+/**
+ * 分销商详情
+ * @param id
+ */
+export async function apiDistributorAccountInfoGetInfo(id: any) {
+  return request(api + `/bookDistributionPlatform/admin/distributorAccount/getInfo/${id}`, {
+    method: 'GET',
+  });
+}
+
+/**
+ * 分销商删除
+ * @param id
+ */
+export async function apiDistributorAccountInfoRemove(id: any) {
+  return request(api + `/bookDistributionPlatform/admin/distributorAccount/remove/${id}`, {
+    method: 'DELETE',
+  });
+}

+ 117 - 0
src/services/distribution/info/index.tsx

@@ -0,0 +1,117 @@
+import { api } from '@/services/api';
+import { request } from '@umijs/max';
+
+/**
+ * 分销商账号状态更新
+ * @param id
+ */
+export async function apiDistributorInfoUpdateEnabled(params: any) {
+  let { id, ...data } = params;
+  return request(api + `/bookDistributionPlatform/admin/distributorAccount/updateEnabled/${id}`, {
+    method: 'PUT',
+    data,
+  });
+}
+/**
+ * 分销商账号更新
+ * @param id
+ * @param companyName   公司名称
+ * @param companyDesc   公司描述
+ * @param companyAddress   地址
+ * @param contactName   姓名
+ * @param contactPhone   电话
+ * @param contactEmail   邮箱
+ * @param remark   备注
+ */
+export async function apiDistributorInfoUpdate(params: any) {
+  let { id, ...data } = params;
+  return request(api + `/bookDistributionPlatform/admin/distributorInfo/update/${id}`, {
+    method: 'PUT',
+    data,
+  });
+}
+
+/**
+ * 分销商账号新增
+ * @param companyName   公司名称
+ * @param companyDesc   公司描述
+ * @param companyAddress   地址
+ * @param contactName   姓名
+ * @param contactPhone   电话
+ * @param contactEmail   邮箱
+ * @param remark   备注
+ */
+export async function apiDistributorInfo(data: any) {
+  return request(api + `/bookDistributionPlatform/admin/distributorInfo`, {
+    method: 'POST',
+    data,
+  });
+}
+/**
+ * 分销商账号小程序新增or修改
+ * @param id
+ * @param distributorId   分销商账号ID
+ * @param appId   小程序ID
+ * @param wxAppConfigParams   小程序配置信息
+ */
+export async function apiDistributorInfoWxAppConfigAddOrEdit(data: any) {
+  return request(api + `/bookDistributionPlatform/admin/distributorInfo/wxAppConfigAddOrEdit`, {
+    method: 'POST',
+    data,
+  });
+}
+
+/**
+ * 分销商账号分页列表
+ * @param pageNum
+ * @param pageSize
+ * @param companyName   公司名称
+ * @param contactName   姓名
+ */
+export async function apiDistributorInfoPageList(params: any) {
+  return request(api + `/bookDistributionPlatform/admin/distributorInfo/pageList`, {
+    method: 'GET',
+    params,
+  });
+}
+/**
+ * 分销商账号列表
+ */
+export async function apiDistributorInfoList(params: any) {
+  return request(api + `/bookDistributionPlatform/admin/distributorInfo/list`, {
+    method: 'GET',
+    params,
+  });
+}
+
+/**
+ * 分销商账号详情
+ * @param id
+ */
+export async function apiDistributorInfoGetInfo(id: any) {
+  return request(api + `/bookDistributionPlatform/admin/distributorInfo/getInfo/${id}`, {
+    method: 'GET',
+  });
+}
+/**
+ * 分销商账号小程序配置信息
+ * @param distributorId
+ */
+export async function apiDistributorInfoGetConfigInfo(distributorId: any) {
+  return request(
+    api + `/bookDistributionPlatform/admin/distributorInfo/getConfigInfo/${distributorId}`,
+    {
+      method: 'GET',
+    },
+  );
+}
+
+/**
+ * 分销商账号删除
+ * @param distributorId
+ */
+export async function apiDistributorInfoRemove(id: any) {
+  return request(api + `/bookDistributionPlatform/admin/distributorInfo/remove/${id}`, {
+    method: 'DELETE',
+  });
+}

+ 64 - 0
src/services/distribution/longBook/index.tsx

@@ -0,0 +1,64 @@
+import { api } from '@/services/api';
+import { request } from '@umijs/max';
+
+/**
+ * 长篇小说付费信息配置
+ */
+export async function apiLongBookInfoBookConfig(data: any) {
+  return request(api + `/bookDistributionPlatform/admin/longBookInfo/bookConfig`, {
+    method: 'POST',
+    data,
+  });
+}
+
+/**
+ * 长篇小说信息列表
+ */
+export async function apiLongBookInfoList(params: any) {
+  return request(api + `/bookDistributionPlatform/admin/longBookInfo/list`, {
+    method: 'GET',
+    params,
+  });
+}
+/**
+ * 长篇小说信息列表全部
+ */
+export async function apiLongBookInfoListAll() {
+  return request(api + `/bookDistributionPlatform/admin/longBookInfo/listAll`, {
+    method: 'GET',
+  });
+}
+/**
+ * 长篇小说章节列表
+ */
+export async function apiLongBookInfoChapterList(params: any) {
+  return request(api + `/bookDistributionPlatform/admin/longBookInfo/chapterList`, {
+    method: 'GET',
+    params,
+  });
+}
+/**
+ * 长篇小说全部章节列表
+ */
+export async function apiLongBookInfoChapterAllList(bookId: any) {
+  return request(api + `/bookDistributionPlatform/admin/longBookInfo/chapterAllList/${bookId}`, {
+    method: 'GET',
+  });
+}
+/**
+ * 长篇小说章节内容
+ */
+export async function apiLongBookInfoChapterContent(chapterId: any) {
+  return request(api + `/bookDistributionPlatform/admin/longBookInfo/chapterContent/${chapterId}`, {
+    method: 'GET',
+  });
+}
+
+/**
+ * 长篇小说付费配置详情
+ */
+export async function apiLongBookInfoConfigDetail(bookId: any) {
+  return request(api + `/bookDistributionPlatform/admin/longBookInfo/configDetail/${bookId}`, {
+    method: 'GET',
+  });
+}

+ 80 - 0
src/services/distribution/miniprogram/index.tsx

@@ -0,0 +1,80 @@
+import { api } from '@/services/api';
+import { request } from '@umijs/max';
+
+/**
+ * 小程序组件配置更新
+ * @param id
+ */
+export async function apiMiniappComponentInfoUpdate(params: any) {
+  let { id, ...data } = params;
+  return request(api + `/bookDistributionPlatform/admin/appComponent/update/${id}`, {
+    method: 'PUT',
+    data,
+  });
+}
+/**
+ * 小程序组件配置状态更新
+ * @param id
+ */
+export async function apiMiniappComponentInfoUpdateEnabled(params: any) {
+  let { id, ...data } = params;
+  return request(api + `/bookDistributionPlatform/admin/appComponent/updateEnabled/${id}`, {
+    method: 'PUT',
+    data,
+  });
+}
+/**
+ * 小程序组件配置新增
+ * @param componentName   组件名称
+ * @param useEcology   组件使用平台
+ * @param defaultComponentConfigList   组件配置参数 [{}]
+ * @param remark   备注
+ */
+export async function apiMiniappComponentInfoSave(data: any) {
+  return request(api + `/bookDistributionPlatform/admin/appComponent/save`, {
+    method: 'POST',
+    data,
+  });
+}
+
+/**
+ * 小程序组件配置分页列表
+ * @param pageNum
+ * @param pageSize
+ * @param componentName   组件名称
+ * @param useEcology   组件使用平台
+ */
+export async function apiMiniappComponentInfoPageList(params: any) {
+  return request(api + `/bookDistributionPlatform/admin/appComponent/pageList`, {
+    method: 'GET',
+    params,
+  });
+}
+/**
+ * 小程序组件配置列表
+ */
+export async function apiMiniappComponentInfoList() {
+  return request(api + `/bookDistributionPlatform/admin/appComponent/list`, {
+    method: 'GET',
+  });
+}
+
+/**
+ * 小程序组件配置详情
+ * @param id
+ */
+export async function apiMiniappComponentInfoGetInfo(id: any) {
+  return request(api + `/bookDistributionPlatform/admin/appComponent/get/${id}`, {
+    method: 'GET',
+  });
+}
+
+/**
+ * 小程序删除
+ * @param id
+ */
+export async function apiMiniappComponentInfoDel(id: any) {
+  return request(api + `/bookDistributionPlatform/admin/appComponent/remove/${id}`, {
+    method: 'DELETE',
+  });
+}

+ 53 - 0
src/services/distribution/orderLog/index.tsx

@@ -0,0 +1,53 @@
+import { api } from '@/services/api';
+import { request } from '@umijs/max';
+
+interface Prams {
+  wechatMiniappId?: string;
+  distributorId?: string;
+  appType?: 1 | 2;
+  bookId?: string;
+  startTime?: string;
+  endTime?: string;
+  userId: string;
+  pageNum: string;
+  pageSize: string;
+}
+
+/**
+ * 短篇小说阅读记录管理
+ * @param {Object} params - 参数对象
+ * @param {string} params.wechatMiniappId - 微信小程序本地 id
+ * @param {string} params.distributorId - 分销商 id
+ * @param {1|2} params.appType - 1微信小程序 2 抖音小程序
+ * @param {string} params.userId - 用户ID
+ * @param {string} params.bookId - 小说ID
+ * @param {string} params.startTime - 开始时间
+ * @param {string} params.endTime - 结束时间
+ * @param {string} params.pageNum - 分页
+ * @param {string} params.pageSize - 数量
+ */
+export async function apiShortBookOrderListOfPage(params: Prams) {
+  return request(api + `/bookDistributionPlatform/admin/shortBookOrder/listOfPage`, {
+    method: 'GET',
+    params,
+  });
+}
+/**
+ * 长篇小说阅读记录管理
+ * @param {Object} params - 参数对象
+ * @param {string} params.wechatMiniappId - 微信小程序本地 id
+ * @param {string} params.distributorId - 分销商 id
+ * @param {1|2} params.appType - 1微信小程序 2 抖音小程序
+ * @param {string} params.userId - 用户ID
+ * @param {string} params.bookId - 小说ID
+ * @param {string} params.startTime - 开始时间
+ * @param {string} params.endTime - 结束时间
+ * @param {string} params.pageNum - 分页
+ * @param {string} params.pageSize - 数量
+ */
+export async function apiLongBookOrderListOfPage(params: Prams) {
+  return request(api + `/bookDistributionPlatform/admin/longBookOrder/listOfPage`, {
+    method: 'GET',
+    params,
+  });
+}

+ 33 - 0
src/services/distribution/payLog/index.tsx

@@ -0,0 +1,33 @@
+import { api } from '@/services/api';
+import { request } from '@umijs/max';
+
+interface Prams {
+  wechatMiniappId?: string;
+  distributorId?: string;
+  appType?: 1 | 2;
+  bookId?: string;
+  startTime?: string;
+  endTime?: string;
+  userId: string;
+  pageNum: string;
+  pageSize: string;
+}
+
+/**
+ * 短篇小说书币变更记录管理
+ */
+export async function apiShortBookCoinChangeLogOfPage(params: Prams) {
+  return request(api + `/bookDistributionPlatform/admin/shortBookCoinChangeLog/listOfPage`, {
+    method: 'GET',
+    params,
+  });
+}
+/**
+ * 长篇小说书币变更录管理
+ */
+export async function apiLongBookCoinChangeLogOfPage(params: Prams) {
+  return request(api + `/bookDistributionPlatform/admin/longBookCoinChangeLog/listOfPage`, {
+    method: 'GET',
+    params,
+  });
+}

+ 33 - 0
src/services/distribution/readLog/index.tsx

@@ -0,0 +1,33 @@
+import { api } from '@/services/api';
+import { request } from '@umijs/max';
+
+interface Prams {
+  wechatMiniappId?: string;
+  distributorId?: string;
+  appType?: 1 | 2;
+  bookId?: string;
+  startTime?: string;
+  endTime?: string;
+  userId: string;
+  pageNum: string;
+  pageSize: string;
+}
+
+/**
+ * 短篇小说订单记录管理
+ */
+export async function apiShortBookReadLogListOfPage(params: Prams) {
+  return request(api + `/bookDistributionPlatform/admin/shortBookReadLog/listOfPage`, {
+    method: 'GET',
+    params,
+  });
+}
+/**
+ * 长篇小说订单记录管理
+ */
+export async function apiLongBookReadLogListOfPage(params: Prams) {
+  return request(api + `/bookDistributionPlatform/admin/longBookReadLog/listOfPage`, {
+    method: 'GET',
+    params,
+  });
+}

+ 60 - 0
src/services/distribution/shortBook/index.tsx

@@ -0,0 +1,60 @@
+import { api } from '@/services/api';
+import { request } from '@umijs/max';
+
+/**
+ * 短篇小说付费信息配置
+ */
+export async function apiShortBookInfoBookConfig(data: any) {
+  return request(api + `/bookDistributionPlatform/admin/shortBookInfo/bookConfig`, {
+    method: 'POST',
+    data,
+  });
+}
+
+/**
+ * 短篇小说信息列表
+ */
+export async function apiShortBookInfoList(params: any) {
+  return request(api + `/bookDistributionPlatform/admin/shortBookInfo/list`, {
+    method: 'GET',
+    params,
+  });
+}
+/**
+ * 短篇小说段落列表
+ */
+export async function apiShortBookInfoChapterList(params: any) {
+  return request(api + `/bookDistributionPlatform/admin/shortBookInfo/paragraphList`, {
+    method: 'GET',
+    params,
+  });
+}
+
+/**
+ * 短篇小说付费配置详情
+ */
+export async function apiShortBookInfoConfigDetail(bookId: any) {
+  return request(api + `/bookDistributionPlatform/admin/shortBookInfo/configDetail/${bookId}`, {
+    method: 'GET',
+  });
+}
+/**
+ * 全部短篇小说列表
+ */
+export async function apiShortBookInfoChapterListAll() {
+  return request(api + `/bookDistributionPlatform/admin/shortBookInfo/listAll`, {
+    method: 'GET',
+  });
+}
+/**
+ * 短篇小说全部章节列表
+ */
+export async function apiShortBookInfoAllList(bookId: any) {
+  return request(
+    api +
+      `/bookDistributionPlatform/admin/shortBookInfo/paragraphAllList/${bookId}?backContent=${true}`,
+    {
+      method: 'GET',
+    },
+  );
+}

+ 24 - 0
src/services/distribution/system/index.tsx

@@ -0,0 +1,24 @@
+import { api } from '@/services/api';
+import { request } from '@umijs/max';
+
+/**
+ * 系统配置信息更新
+ * @param id
+ * @param configValue   配置值
+ * @param remark   备注
+ */
+export async function apiSysConfigParamsUpdate(data: any) {
+  return request(api + `/bookDistributionPlatform/admin/sysConfigParams/update`, {
+    method: 'PUT',
+    data,
+  });
+}
+
+/**
+ * 系统配置信息列表
+ */
+export async function apiSysConfigParamsList() {
+  return request(api + `/bookDistributionPlatform/admin/sysConfigParams/list`, {
+    method: 'GET',
+  });
+}

+ 106 - 0
src/services/distribution/weChatInfo/index.tsx

@@ -0,0 +1,106 @@
+import { api } from '@/services/api';
+import { request } from '@umijs/max';
+
+/**
+ * 微信小程序状态更新
+ * @param id
+ */
+export async function apiWxAppInfoUpdateEnabled(params: any) {
+  let { id, ...data } = params;
+  return request(api + `/bookDistributionPlatform/admin/wechatMiniapp/updateEnabled/${id}`, {
+    method: 'PUT',
+    data,
+  });
+}
+/**
+ * 微信小程序信息更新
+ * @param id
+ * @param appId   小程序appId
+ * @param appSecret   小程序秘钥
+ * @param appName   小程序名称
+ * @param homePage   小程序首页链接
+ * @param appVersion   小程序版本号
+ * @param configParams   小程序配置信息(json)
+ * @param remark   备注
+ *
+ */
+export async function apiWxAppInfoUpdate(params: any) {
+  let { id, ...data } = params;
+  return request(api + `/bookDistributionPlatform/admin/wechatMiniapp/update/${id}`, {
+    method: 'PUT',
+    data,
+  });
+}
+
+/**
+ * 微信小程序信息新增
+ * @param appId   小程序appId
+ * @param appSecret   小程序秘钥
+ * @param appName   小程序名称
+ * @param homePage   小程序首页链接
+ * @param appVersion   小程序版本号
+ * @param configParams   小程序配置信息(json)
+ * @param remark   备注
+ */
+export async function apiWxAppInfo(data: any) {
+  return request(api + `/bookDistributionPlatform/admin/wechatMiniapp`, {
+    method: 'POST',
+    data,
+  });
+}
+
+/**
+ * 微信小程序分页列表
+ * @param pageNum
+ * @param pageSize
+ * @param appId   小程序ID
+ * @param appName   小程序名称
+ */
+export async function apiWxAppInfoPageList(params: any) {
+  return request(api + `/bookDistributionPlatform/admin/wechatMiniapp/pageList`, {
+    method: 'GET',
+    params,
+  });
+}
+/**
+ * 微信小程序列表
+ */
+export async function apiWxAppInfoList() {
+  return request(api + `/bookDistributionPlatform/admin/wechatMiniapp/list`, {
+    method: 'GET',
+  });
+}
+
+/**
+ * 小程序详情
+ * @param id
+ */
+export async function apiWxAppInfoGetInfo(id: any) {
+  return request(api + `/bookDistributionPlatform/admin/wechatMiniapp/getInfo/${id}`, {
+    method: 'GET',
+  });
+}
+
+/**
+ * 小程序删除
+ * @param id
+ */
+export async function apiWxAppInfoRemove(id: any) {
+  return request(api + `/bookDistributionPlatform/admin/wechatMiniapp/remove/${id}`, {
+    method: 'DELETE',
+  });
+}
+/**小程序指派 */
+
+export async function apiWxAppInfoWxAppConfigAddOrEdit(data: any) {
+  return request(api + `/bookDistributionPlatform/admin/wechatMiniapp/configDistribution`, {
+    method: 'POST',
+    data,
+  });
+}
+/**商户列*/
+export async function wechatMchAll() {
+  return request(api + `/bookDistributionPlatform/admin/wechatMch/all`, {
+    method: 'POST',
+  });
+}

+ 52 - 12
src/services/global.ts

@@ -2,22 +2,62 @@ import { api } from '@/services/api';
 import { generateNewLocalMenu } from '@/utils/generateNewLocalMenu';
 import { request } from '@umijs/max';
 import routes from '../../config/routes';
+export type MediaTypeProps = 'image';
 /**
  * @param data 菜单参数 ["book"]
  */
-export async function getMenu(data:any[]): Promise<any> {
+export async function getMenu(data: any[]): Promise<any> {
   return request(api + '/erp/menu/getRouters', {
     method: 'PUT',
-    data
-  }).then(res=>{
-      let menus:any = []
-      for(let name of data){
-        let arr = generateNewLocalMenu(routes,res.data[name])
-        menus = [...menus,...arr]
-      }
-      console.log("menus",menus)
-     return menus
-  }).catch(err=>{
-    console.log(err)
+    data,
   })
+    .then((res) => {
+      let menus: any = [];
+      for (let name of data) {
+        let arr = generateNewLocalMenu(routes, res.data[name]);
+        menus = [...menus, ...arr];
+      }
+      console.log('menus', menus);
+      return menus;
+    })
+    .catch((err) => {
+      console.log(err);
+    });
+}
+
+/**
+ * 上传
+ * @param params
+ * @returns
+ */
+export async function upLoadMediaApi(data: {
+  mediaType: MediaTypeProps;
+  multipartFile: FormData;
+  suffix: string;
+}) {
+  let { multipartFile, ...params } = data;
+  return request(api + `/bookContent/corp/media/upload`, {
+    method: 'POST',
+    data: multipartFile,
+    params,
+  });
+}
+
+/**
+ * 获取OSS密钥
+ * @param params filePrefix 文件名不带后缀 suffix 文件类型
+ * @returns
+ */
+export async function getOssSecretKeyApi(params: { type: string }) {
+  return request(api + `/bookContent/oss/form/upload`, {
+    method: 'GET',
+    params,
+  });
+}
+
+/**枚举列表 */
+export async function enumDictList() {
+  return request(api + `/bookDistributionPlatform/admin/enumDict/list`, {
+    method: 'GET',
+  });
 }

+ 8 - 8
src/services/login/index.tsx

@@ -1,23 +1,23 @@
 import { request } from '@umijs/max';
 import { api } from '../api';
-console.log(api,process.env.NODE_ENV)
+console.log(api, process.env.NODE_ENV);
 
 // 手机钉钉验证码获取
 export async function getCode(phone: string) {
-  return request(api + `/erp/user/dCodeLoginState/${phone}`)
+  return request(api + `/erp/user/dCodeLoginState/${phone}`);
 }
 // 手机短信验证码获取
 export async function getNoteCode(phone: string) {
-  return request(api + `/erp/user/smsCodeLoginState/${phone}`)
+  return request(api + `/erp/user/smsCodeLoginState/${phone}`);
 }
 // 手机登录
-export async function phoneLogin(params: { phone: string, code: string }) {
-  console.log('------------>', params)
+export async function phoneLogin(params: { phone: string; code: string }) {
+  console.log('------------>', params);
   return request(api + `/erp/user/dCodeLogin`, {
     method: 'POST',
-    data: params
-  })
+    data: params,
+  });
 }
 export async function getUserInfo() {
   return request(api + '/erp/user/loginUserInfo');
-}
+}

+ 0 - 12
src/services/swagger/index.ts

@@ -1,12 +0,0 @@
-// @ts-ignore
-/* eslint-disable */
-// API 更新时间:
-// API 唯一标识:
-import * as pet from './pet';
-import * as store from './store';
-import * as user from './user';
-export default {
-  pet,
-  store,
-  user,
-};

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff