2 Achegas e61b367b5c ... d84850e489

Autor SHA1 Mensaxe Data
  code4eat d84850e489 添加kc-ai功能 hai 4 meses
  code4eat c5b1edbc70 年度提交 hai 7 meses
Modificáronse 97 ficheiros con 8931 adicións e 2547 borrados
  1. 51 36
      config/config.ts
  2. 16 10
      config/proxy.ts
  3. 2 0
      package.json
  4. BIN=BIN
      public/images/ai-eyes.png
  5. BIN=BIN
      public/images/ai.png
  6. BIN=BIN
      public/images/ai_descr.png
  7. BIN=BIN
      public/images/appbankuaiguanli.png
  8. BIN=BIN
      public/images/appbankuaiguanli_gray.png
  9. BIN=BIN
      public/images/empty_bigger.png
  10. BIN=BIN
      public/images/indexBg.png
  11. BIN=BIN
      public/images/indexBgCenter.png
  12. BIN=BIN
      public/images/pcbankuaiguanli.png
  13. BIN=BIN
      public/images/pcbankuaiguanli_gray.png
  14. BIN=BIN
      public/images/send_white.png
  15. BIN=BIN
      public/images/tiaozhenghou.png
  16. BIN=BIN
      public/images/tiaozhengqian.png
  17. BIN=BIN
      public/images/xitongtishichatu.png
  18. 0 0
      public/w.ico
  19. 1 1
      public/zhongtaiA.js
  20. 0 0
      public/zhongtaiB.js
  21. 0 0
      public/zhongtaiC.js
  22. 53 41
      src/app.tsx
  23. 28 38
      src/components/KCInput/index.tsx
  24. 142 107
      src/components/NavSelecter/index.tsx
  25. 24 0
      src/components/emptyPage/index.tsx
  26. 98 0
      src/components/emptyPage/style.less
  27. 59 0
      src/components/intelligenceBot/AutoResizingTextarea/index.tsx
  28. 23 0
      src/components/intelligenceBot/AutoResizingTextarea/style.less
  29. 241 0
      src/components/intelligenceBot/Chat/index.tsx
  30. 113 0
      src/components/intelligenceBot/Chat/style.less
  31. BIN=BIN
      src/components/intelligenceBot/images/ai-avator.png
  32. BIN=BIN
      src/components/intelligenceBot/images/ai-eyes.png
  33. BIN=BIN
      src/components/intelligenceBot/images/ai.png
  34. BIN=BIN
      src/components/intelligenceBot/images/ai_descr.png
  35. BIN=BIN
      src/components/intelligenceBot/images/send_white.png
  36. 369 0
      src/components/intelligenceBot/index.tsx
  37. 218 0
      src/components/intelligenceBot/style.less
  38. BIN=BIN
      src/components/topBar/images/groupIcon.png
  39. 234 191
      src/components/topBar/index.tsx
  40. 84 2
      src/components/topBar/style.less
  41. 4 8
      src/global.less
  42. 61 32
      src/layouts/index.tsx
  43. 143 0
      src/layouts/style.less
  44. 1 2
      src/pages/document.ejs
  45. 20 94
      src/pages/index/components/FastEntry/index.tsx
  46. 52 33
      src/pages/index/index.tsx
  47. 40 0
      src/pages/login/ErrorBoundary.tsx
  48. 67 56
      src/pages/login/index.tsx
  49. 46 63
      src/pages/platform/_layout.tsx
  50. 31 0
      src/pages/platform/home/index.tsx
  51. 8 0
      src/pages/platform/home/service.ts
  52. 94 0
      src/pages/platform/home/style.less
  53. 42 46
      src/pages/platform/index.less
  54. 48 0
      src/pages/platform/setting/dataFilling/fillingMana/authHisttory/index.tsx
  55. 33 0
      src/pages/platform/setting/dataFilling/fillingMana/authHisttory/style.less
  56. 99 0
      src/pages/platform/setting/dataFilling/fillingMana/generateTableData.tsx
  57. 652 0
      src/pages/platform/setting/dataFilling/fillingMana/index.tsx
  58. 73 0
      src/pages/platform/setting/dataFilling/fillingMana/service.ts
  59. 163 0
      src/pages/platform/setting/dataFilling/fillingMana/style.less
  60. 48 0
      src/pages/platform/setting/dataFilling/mineFilling/authHisttory/index.tsx
  61. 33 0
      src/pages/platform/setting/dataFilling/mineFilling/authHisttory/style.less
  62. 261 0
      src/pages/platform/setting/dataFilling/mineFilling/generateTableData.tsx
  63. 211 0
      src/pages/platform/setting/dataFilling/mineFilling/index.tsx
  64. 34 0
      src/pages/platform/setting/dataFilling/mineFilling/service.ts
  65. 244 0
      src/pages/platform/setting/dataFilling/mineFilling/style.less
  66. 320 297
      src/pages/platform/setting/departmentMana/index.tsx
  67. 105 0
      src/pages/platform/setting/embeddedDashboard/index.tsx
  68. 0 0
      src/pages/platform/setting/embeddedDashboard/service.ts
  69. 9 0
      src/pages/platform/setting/embeddedDashboard/style.less
  70. 35 26
      src/pages/platform/setting/hospManage/index.tsx
  71. 15 14
      src/pages/platform/setting/hospManage/modals/modal.tsx
  72. 421 446
      src/pages/platform/setting/hospParamsMana/index.tsx
  73. 1133 0
      src/pages/platform/setting/indicatoLagacy/ DrawerForm/drawer.tsx
  74. 70 0
      src/pages/platform/setting/indicatoLagacy/ DrawerForm/style.less
  75. 86 0
      src/pages/platform/setting/indicatoLagacy/TreeDirectory/index.tsx
  76. 0 0
      src/pages/platform/setting/indicatoLagacy/TreeDirectory/style.less
  77. 469 0
      src/pages/platform/setting/indicatoLagacy/index.tsx
  78. 74 0
      src/pages/platform/setting/indicatoLagacy/style.less
  79. 674 483
      src/pages/platform/setting/indicatorMana/DrawerForm/drawer.tsx
  80. 27 14
      src/pages/platform/setting/indicatorMana/DrawerForm/style.less
  81. 63 0
      src/pages/platform/setting/indicatorMana/authHisttory/index.tsx
  82. 33 0
      src/pages/platform/setting/indicatorMana/authHisttory/style.less
  83. 373 229
      src/pages/platform/setting/indicatorMana/index.tsx
  84. 245 31
      src/pages/platform/setting/indicatorMana/style.less
  85. 19 20
      src/pages/platform/setting/pubDicMana/index.tsx
  86. 15 7
      src/pages/platform/setting/pubDicTypeMana/index.tsx
  87. 42 26
      src/pages/platform/setting/roleManage/index.tsx
  88. 104 29
      src/pages/platform/setting/roleManage/modals/modal.tsx
  89. 2 0
      src/pages/platform/setting/roleManage/model.ts
  90. 27 32
      src/pages/platform/setting/systemNavMana/index.tsx
  91. 85 38
      src/pages/platform/setting/userManage/index.tsx
  92. 113 64
      src/pages/platform/setting/userManage/modal.tsx
  93. 17 0
      src/service/ai.ts
  94. 9 1
      src/service/dictionary.ts
  95. 17 1
      src/service/index.ts
  96. 61 28
      src/service/indicator.ts
  97. 4 1
      src/service/role.ts

+ 51 - 36
config/config.ts

@@ -1,7 +1,7 @@
 /*
  * @Author: your name
  * @Date: 2022-01-07 10:04:20
- * @LastEditTime: 2024-12-03 11:09:39
+ * @LastEditTime: 2025-03-27 11:12:39
  * @LastEditors: code4eat awesomedema@gmail.com
  * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  * @FilePath: /KC-MiddlePlatform/config/config.ts
@@ -14,7 +14,7 @@ import proxy from './proxy';
 const { REACT_APP_ENV } = process.env;
 
 export default defineConfig({
-  hash:true,
+  hash: true,
   nodeModulesTransform: {
     type: 'none',
   },
@@ -24,6 +24,7 @@ export default defineConfig({
     immer: true,
     hmr: true,
   },
+  extraBabelIncludes: ['/@superset-ui/embedded-sdk/'],
   antd: {
     config: {
       prefixCls: 'kcmp-ant',
@@ -137,6 +138,14 @@ export default defineConfig({
             //loader: (loading:boolean) => <div>loading</div>,
           },
         },
+        {
+          path: '/nurseManageSystem',
+          microApp: 'nurseManageSystem',
+          microAppProps: {
+            autoCaptureError: true,
+            //loader: (loading:boolean) => <div>loading</div>,
+          },
+        },
         {
           path: '/channelIndex/channelIndexOne',
           component: '@/pages/channelIndex/channelIndexOne/index.tsx',
@@ -151,7 +160,6 @@ export default defineConfig({
           component: './noAccess',
         },
         {
-          title: '精益管管理中台',
           path: '/platform',
           component: '@/pages/platform/_layout.tsx',
           routes: [
@@ -159,9 +167,17 @@ export default defineConfig({
               path: '/platform/sqlEditer',
               component: '@/pages/platform/sqlediter/index.tsx',
             },
+            {
+              path: '/platform/home',
+              component: '@/pages/platform/home/index.tsx',
+            },
             {
               path: '/platform/setting',
               routes: [
+                {
+                  path: '/platform/setting/embeddedDashboard/:dashboardId',
+                  component: '@/pages/platform/setting/embeddedDashboard/index.tsx',
+                },
                 {
                   path: '/platform/setting/userManage',
                   component: '@/pages/platform/setting/userManage/index.tsx',
@@ -173,16 +189,12 @@ export default defineConfig({
                 {
                   path: '/platform/setting/menuManage',
                   component: '@/pages/platform/setting/menuManage/index.tsx',
-                  wrappers: [
-                    '@/wrappers/auth',
-                  ],
+                  wrappers: ['@/wrappers/auth'],
                 },
                 {
                   path: '/platform/setting/roleManage',
                   component: '@/pages/platform/setting/roleManage/index.tsx',
-                  wrappers: [
-                    '@/wrappers/auth',
-                  ],
+                  wrappers: ['@/wrappers/auth'],
                 },
                 // {
                 //   path: '/platform/setting/reports',
@@ -191,64 +203,67 @@ export default defineConfig({
                 {
                   path: '/platform/setting/departmentMana',
                   component: '@/pages/platform/setting/departmentMana/index.tsx',
-                  wrappers: [
-                    '@/wrappers/auth',
-                  ],
+                  wrappers: ['@/wrappers/auth'],
                 },
                 {
                   path: '/platform/setting/pubDicTypeMana',
                   component: '@/pages/platform/setting/pubDicTypeMana/index.tsx',
-                  wrappers: [
-                    '@/wrappers/auth',
-                  ],
+                  wrappers: ['@/wrappers/auth'],
                 },
                 {
                   path: '/platform/setting/hospParamsMana',
                   component: '@/pages/platform/setting/hospParamsMana/index.tsx',
-                  wrappers: [
-                    '@/wrappers/auth',
-                  ],
+                  wrappers: ['@/wrappers/auth'],
                 },
                 {
                   path: '/platform/setting/pubDicMana/:type',
                   component: '@/pages/platform/setting/pubDicMana/index.tsx',
-                  wrappers: [
-                    '@/wrappers/auth',
-                  ],
+                  wrappers: ['@/wrappers/auth'],
                 },
                 {
                   path: '/platform/setting/indicatorMana',
                   component: '@/pages/platform/setting/indicatorMana/index.tsx',
-                  wrappers: [
-                    '@/wrappers/auth',
-                  ],
+                  wrappers: ['@/wrappers/auth'],
+                },
+                {
+                  path: '/platform/setting/indicatoLagacy',
+                  component: '@/pages/platform/setting/indicatoLagacy/index.tsx',
+                  wrappers: ['@/wrappers/auth'],
                 },
                 {
                   path: '/platform/setting/paramsMana',
                   component: '@/pages/platform/setting/paramsMana/index.tsx',
-                  wrappers: [
-                    '@/wrappers/auth',
-                  ],
+                  wrappers: ['@/wrappers/auth'],
                 },
                 {
                   path: '/platform/setting/systemNavMana',
                   component: '@/pages/platform/setting/systemNavMana/index.tsx',
-                  wrappers: [
-                    '@/wrappers/auth',
-                  ],
+                  wrappers: ['@/wrappers/auth'],
                 },
                 {
                   path: '/platform/setting/notificationTemplate',
                   component: '@/pages/platform/setting/notificationTemplate/index.tsx',
-                  wrappers: [
-                    '@/wrappers/auth',
-                  ],
+                  wrappers: ['@/wrappers/auth'],
                 },
                 {
                   path: '/platform/setting/kcClassification',
                   component: '@/pages/platform/setting/kcClassification/index.tsx',
-                  wrappers: [
-                    '@/wrappers/auth',
+                  wrappers: ['@/wrappers/auth'],
+                },
+                {
+                  path: '/platform/setting/dataFilling',
+                  name: '数据填报',
+                  routes: [
+                    {
+                      name: '填报管理',
+                      path: '/platform/setting/dataFilling/fillingMana',
+                      component: '@/pages/platform/setting/dataFilling/fillingMana/index',
+                    },
+                    {
+                      name: '我的填报',
+                      path: '/platform/setting/dataFilling/mineFilling',
+                      component: '@/pages/platform/setting/dataFilling/mineFilling/index',
+                    },
                   ],
                 },
               ],
@@ -266,7 +281,7 @@ export default defineConfig({
       ],
     },
   ],
-  // hash:true,
+
   proxy: proxy[REACT_APP_ENV || 'dev'],
   manifest: {
     basePath: '/',

+ 16 - 10
config/proxy.ts

@@ -2,32 +2,38 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2024-04-09 18:07:34
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-10-25 09:34:46
+ * @LastEditTime: 2025-03-27 14:03:32
  * @FilePath: /KC-MiddlePlatform/config/proxy.ts
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
 
-
 const proxy: { [key: string]: any } = {
   dev: {
- 
     '/gateway': {
       //target: 'http://47.96.149.190:5000',
       target: 'http://120.27.235.181:5000',
+      //target: 'http://platform.pre.bs.qjczt.com:5000',
       changeOrigin: true,
       // pathRewrite: { '^/master': '' },
     },
-
     '/api': {
-      target: 'http://120.27.235.181:8083/',
+      target: 'http://localhost:8088',
       changeOrigin: true,
-      pathRewrite: { '^/api': '' },
     },
-
-    '/view': {
-      target: 'http://120.27.235.181:8085/',
+    '/ai': {
+      target: 'http://192.168.0.120:5003',
+      //target: 'http://116.62.47.88:5003',
       changeOrigin: true,
-      // pathRewrite: { '^/view': '' },
+      pathRewrite: { '^/ai': '' },
+      selfHandleResponse: true,
+      onProxyRes: function onProxyRes(proxyRes: any, req: any, res: any) {
+        proxyRes.on('data', function (data: any) {
+          res.write(data);
+        });
+        proxyRes.on('end', function () {
+          res.end();
+        });
+      },
     },
   },
   mock: {

+ 2 - 0
package.json

@@ -29,6 +29,7 @@
     "@ant-design/pro-layout": "^6.38.13",
     "@ant-design/pro-table": "^2.30.8",
     "@monaco-editor/react": "^4.4.5",
+    "@superset-ui/embedded-sdk": "^0.1.3",
     "antd": "^4.21.6",
     "axios": "^1.3.4",
     "cross-env": "^7.0.3",
@@ -37,6 +38,7 @@
     "react": "16.x",
     "react-dev-inspector": "^1.1.1",
     "react-dom": "16.x",
+    "react-markdown": "^8.0.0",
     "umi": "^3.5.20"
   },
   "devDependencies": {

BIN=BIN
public/images/ai-eyes.png


BIN=BIN
public/images/ai.png


BIN=BIN
public/images/ai_descr.png


BIN=BIN
public/images/appbankuaiguanli.png


BIN=BIN
public/images/appbankuaiguanli_gray.png


BIN=BIN
public/images/empty_bigger.png


BIN=BIN
public/images/indexBg.png


BIN=BIN
public/images/indexBgCenter.png


BIN=BIN
public/images/pcbankuaiguanli.png


BIN=BIN
public/images/pcbankuaiguanli_gray.png


BIN=BIN
public/images/send_white.png


BIN=BIN
public/images/tiaozhenghou.png


BIN=BIN
public/images/tiaozhengqian.png


BIN=BIN
public/images/xitongtishichatu.png


+ 0 - 0
public/favicon.ico → public/w.ico


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1 - 1
public/zhongtaiA.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
public/zhongtaiB.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
public/zhongtaiC.js


+ 53 - 41
src/app.tsx

@@ -7,11 +7,12 @@ import { UserDataType, getQiankunMicroApps } from '@/service/login';
 
 import { BasicLayoutProps } from '@ant-design/pro-layout';
 import { logoutHandle } from './global';
-import { Platforms } from './constant';
+import { KcimCenterSysId, Platforms } from './constant';
 import { SpacicalPageParamsType } from './typings';
 import { createFromIconfontCN } from '@ant-design/icons';
-import { handleLogin } from './pages/login';
-
+import { changeFavicon, getHospType, handleLogin } from './pages/login';
+import { getSysParamsByCode } from './service';
+import '../public/zhongtaiC';
 
 window.isParentApp = true;
 
@@ -38,20 +39,25 @@ export const initialStateConfig = {
 
 type InitialStateType = {
   navData: any[];
-  menuData: any[],//中台菜单
-  userData?: UserDataType;
+  menuData: any[]; //中台菜单
+  customerType: string; //客户类型
+  userData?: UserDataType; //登录返回用户信息
+  userInfo: any; //当前用户详细信息
   systemLists?: TopBar.Tab[]; //当前医院可选子系统列表
   openedSysLists?: TopBar.Tab[]; //当前已打开的系统列表
   currentSelectedSys?: TopBar.Tab; //当前选中的tab
   logout?: () => Promise<boolean>;
   childAppIsShowMenu?: boolean;
+  pageTitle: string;
   spacicalPageParamsType?: SpacicalPageParamsType[];
   getHospSubSystemListFunc?: () => Promise<any[]>;
 };
 
+interface MyRequestOptions extends RequestOptionsInit {
+  prefix?: string; // 自定义前缀
+}
 
 export async function getInitialState(): Promise<InitialStateType> {
-
   const fetchUserInfo = async () => {
     try {
       const queryParams = new URLSearchParams(location.search);
@@ -99,10 +105,6 @@ export async function getInitialState(): Promise<InitialStateType> {
     return undefined;
   };
 
-  const getAppIcon = (name: string) => {
-    return Platforms.filter((i) => i.name == name).length > 0 ? Platforms.filter((i) => i.name == name)[0].logo : '';
-  };
-
   //获取当前账号所有子应用列表
   const getHospSubSystemListFunc = async () => {
     // const data = await getHospSubSystemList();
@@ -118,11 +120,15 @@ export async function getInitialState(): Promise<InitialStateType> {
     return [];
   };
 
-
   const logout = logoutHandle;
-
   const userData = await fetchUserInfo();
 
+  let customerType = undefined;
+
+  if (userData?.token) {
+    customerType = await getHospType();
+  }
+
   let systemLists: userRelationInfo.OwnAppsItem[] = [];
   // let navData:any[] = [];
   if (userData) {
@@ -130,11 +136,17 @@ export async function getInitialState(): Promise<InitialStateType> {
     // navData = await getUserPlatformNav();
   }
 
-
-
   const localInitData = localStorage.getItem('initialState');
   const currentSelectedTab = localStorage.getItem('currentSelectedTab');
+  const { loginPic } = JSON.parse(localStorage.getItem('currentSelectedSubHop') as string);
+  const userInfo = JSON.parse(localStorage.getItem('userInfo') as string);
 
+  if (loginPic.length > 0) {
+    const arr = loginPic.split('|');
+    if (arr.length == 2) {
+      changeFavicon(arr[1]);
+    }
+  }
 
   return {
     currentSelectedSys: currentSelectedTab != null ? JSON.parse(currentSelectedTab) : undefined,
@@ -142,14 +154,18 @@ export async function getInitialState(): Promise<InitialStateType> {
     ...JSON.parse(localInitData ? localInitData : '{}'), //覆盖,恢复tab状态
     userData,
     logout,
+    pageTitle: '欢迎进入医管平台',
     favicon: '',
+    customerType,
+    userInfo,
     spacicalPageParamsType: [],
     getHospSubSystemListFunc,
     systemLists: systemLists,
   };
 }
 
-const requestInterceptorsHandle = (url: string, options: RequestOptionsInit) => {
+const requestInterceptorsHandle = (url: string, options: MyRequestOptions) => {
+  const prefix = options.prefix || '/gateway';
   const userData = localStorage.getItem('userData');
   let authHeader = { token: '' };
 
@@ -159,8 +175,10 @@ const requestInterceptorsHandle = (url: string, options: RequestOptionsInit) =>
       authHeader.token = token;
     }
   }
+  const finalUrl = url.startsWith(prefix) ? url : `${prefix}${url}`;
+
   return {
-    url: `/gateway${url}`,
+    url: finalUrl,
     options: { ...options, interceptors: true, headers: authHeader, timeout: 100000000 },
   };
 };
@@ -272,7 +290,7 @@ const errorHandlerFunc = (error: ResponseErr) => {
         message: ` ${errorCode}:出现错误!`,
         description: errorMessage,
         icon: <IconFont type="icon-jinggaotishi" />,
-        style: { padding: 16, margin: 0, borderRadius: 8 }
+        style: { padding: 16, margin: 0, borderRadius: 8 },
       });
     } else if (false) {
     } else {
@@ -281,7 +299,7 @@ const errorHandlerFunc = (error: ResponseErr) => {
         message: '错误',
         description: ` ${errorCode}:${errorMessage}`,
         icon: <IconFont type="icon-jinggaotishi" />,
-        style: { padding: 16, margin: 0, borderRadius: 8 }
+        style: { padding: 16, margin: 0, borderRadius: 8 },
       });
     }
   } catch (err) {
@@ -291,7 +309,7 @@ const errorHandlerFunc = (error: ResponseErr) => {
       message: '提示',
       description: '遇到未知错误,查看控制台!',
       icon: <IconFont type="icon-jinggaotishi" />,
-      style: { padding: 16, margin: 0, borderRadius: 8 }
+      style: { padding: 16, margin: 0, borderRadius: 8 },
     });
   }
 };
@@ -340,7 +358,7 @@ export const qiankun = async () => {
         // },
         {
           name: 'nursingWorkersManaSystem', // 唯一 id
-          entry: '//116.169.61.56:29528',  //测试
+          entry: '//116.169.61.56:29528', //测试
         },
         // {
         //   name: 'reviewMana', // 唯一 id
@@ -350,8 +368,8 @@ export const qiankun = async () => {
         // },
         {
           name: 'budgetManaSystem', // 唯一 id
-          //entry: '//localhost:8002',
-          entry: '//120.27.235.181:5000/perform/',  //开发
+          entry: '//localhost:8002',
+          //entry: '//120.27.235.181:5000/perform/',  //开发
           //entry: '//47.96.149.190:5000/perform/', //演示
           //entry: '//198.198.203.161:5000/perform/', //淮南
         },
@@ -362,20 +380,20 @@ export const qiankun = async () => {
         // },
         {
           name: 'CostAccountingSys', // 唯一 id
-          entry: '//localhost:8002',
-          //entry: '//120.27.235.181:5000/costAccount/', // 开发
+          //entry: '//localhost:8001',
+          entry: '//120.27.235.181:5000/costAccount/', // 开发
         },
         {
           name: 'MediResourceManaSys', // 唯一 id
-          entry: '//localhost:8001',
-          //entry: '//120.27.235.181:5000/costAccount/', // 开发
-        },
-        {
-          name: 'MedicalWisdomCheckSys', // 唯一 id
-          entry: '//localhost:8804',
-          //entry: '//120.27.235.181:5000/pfmview/', // 开发
+          //entry: '//localhost:8001',
+          entry: '//120.27.235.181:5000/costAccount/', // 开发
         },
         // {
+        //   name: 'MedicalWisdomCheckSys', // 唯一 id
+        //   entry: '//localhost:8804',
+        //   //entry: '//120.27.235.181:5000/pfmview/', // 开发
+        // },
+        // {
         //   name: 'personnelManaSystem', // 唯一 id
         //   entry: '//192.168.0.118:8005'
         //   //entry: '//120.27.235.181:5000/costAccount/', // 开发
@@ -409,7 +427,6 @@ export const qiankun = async () => {
   }
 };
 
-
 //向子应用透传
 export function useQiankunStateForSlave() {
   const [masterState, setMasterState] = useState({});
@@ -420,9 +437,6 @@ export function useQiankunStateForSlave() {
   };
 }
 
-
-
-
 // //@/pages/platform/setting/reports/index
 export function patchRoutes({ routes }: { routes: any }) {
   const treeLoop = (treeData: any) => {
@@ -460,18 +474,16 @@ export function patchRoutes({ routes }: { routes: any }) {
   treeLoop(routes[0]);
 }
 
-export const layout = ({ initialState: { } }: { initialState: InitialStateType }): BasicLayoutProps => {
+export const layout = ({ initialState }: { initialState?: InitialStateType }): BasicLayoutProps => {
+  const { navData = [], menuData = [], customerType = '' } = initialState || {};
+
   return {
     headerRender: false,
     rightContentRender: () => <>right</>,
     footerRender: () => null,
     onPageChange: () => {
-      // //如果没有登录,重定向到 login
-      // if (!userData && location.pathname !== '/login') {
-      //   // history.push('/login');
-      // }
+      // 页面变化时的逻辑
     },
     menuHeaderRender: undefined,
-    // ...initialState?.settings,
   };
 };

+ 28 - 38
src/components/KCInput/index.tsx

@@ -2,56 +2,46 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2023-03-03 15:18:14
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2023-03-03 17:15:26
+ * @LastEditTime: 2025-02-18 15:09:20
  * @FilePath: /KC-MiddlePlatform/src/components/KCInput/index.tsx
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
 
-
 import { createFromIconfontCN } from '@ant-design/icons';
-import {ProFormText } from '@ant-design/pro-form'
-import { ProFormFieldItemProps } from '@ant-design/pro-form/lib/interface'
+import { ProFormText } from '@ant-design/pro-form';
+import { ProFormFieldItemProps } from '@ant-design/pro-form/lib/interface';
 
 import { Input, InputProps, InputRef } from 'antd';
 import Password from 'antd/lib/input/Password';
 
-
-
 export type KCInput = ProFormFieldItemProps<InputProps, InputRef> & {
-    Password?: typeof Password;
-    search?:boolean; //开启搜索输入
-    onChange?:(e: any)=>void;
-    onSearch?:()=>void;
-    style?: React.CSSProperties | undefined;
-    placeholder?: string | undefined
-
-}
+  Password?: typeof Password;
+  search?: boolean; //开启搜索输入
+  onChange?: (e: any) => void;
+  onSearch?: () => void;
+  style?: React.CSSProperties | undefined;
+  placeholder?: string | undefined;
+};
 
 const IconFont = createFromIconfontCN({
-    scriptUrl: '//at.alicdn.com/t/c/font_1927152_4nm5kxbv4m3.js',
+  scriptUrl: '',
 });
 
-
-export const KCInput = (props:KCInput) => {
-
-  const {
-    fieldProps,search,onChange,onSearch = ()=>{},
-    style,
-    placeholder,
-    ...rest
-  } = props;
-
-  if(search){
-     return (
-        <Input allowClear  onChange={(e)=>onChange&&onChange(e)} placeholder={placeholder} style={{...style}} suffix={
-            <IconFont type="iconsousuo" onClick={() => onSearch()} />
-        }
-        onPressEnter={()=>onSearch()}
-        />
-     )
+export const KCInput = (props: KCInput) => {
+  const { fieldProps, search, onChange, onSearch = () => {}, style, placeholder, ...rest } = props;
+
+  if (search) {
+    return (
+      <Input
+        allowClear
+        onChange={(e) => onChange && onChange(e)}
+        placeholder={placeholder}
+        style={{ ...style }}
+        suffix={<IconFont type="iconsousuo" onClick={() => onSearch()} />}
+        onPressEnter={() => onSearch()}
+      />
+    );
   }
-  
-  return (
-    <ProFormText  {...rest} style={{...style}} placeholder={placeholder} fieldProps={{...fieldProps}} />
-  )
-}
+
+  return <ProFormText {...rest} style={{ ...style }} placeholder={placeholder} fieldProps={{ ...fieldProps }} />;
+};

+ 142 - 107
src/components/NavSelecter/index.tsx

@@ -1,20 +1,14 @@
-/*
- * @Author: code4eat awesomedema@gmail.com
- * @Date: 2022-06-29 11:05:04
- * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-01-16 16:37:52
- * @FilePath: /KC-MiddlePlatform/src/components/NavSelecter/index.tsx
- * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
- */
-
 import { Tabs, Checkbox, Button, message } from 'antd';
 import React, { Key, useEffect, useState } from 'react';
 import './style.less';
 import { findNodesBySomes, getLeafNodes } from '@/utils';
+import { CheckboxChangeEvent } from 'antd/lib/checkbox';
+import { removeFastEntrance } from '@/service';
 
 const { TabPane } = Tabs;
 
 export type NavSelecterItemType = {
+  id: string;
   name: string;
   menuId: number | string;
   path: string;
@@ -33,146 +27,187 @@ export type NavSelecterData = {
   child?: NavSelecterData[];
 };
 
-export interface NavSelecter {
+export interface NavSelecterProps {
   onVisibleChange?: (t: boolean) => void;
   onChecked?: (t: NavSelecterItemType[]) => void;
-  value?: Key[];
+  value?: NavSelecterItemType[] | Key[];
   data?: NavSelecterData[];
   title?: string;
-  type?: number; //1 默认 2 限制最多可选个数
+  type?: number; // 1 默认 2 限制最多可选个数
+  resetHandleType?: 'default' | 'request';
 }
 
-const NavSelecter = (props: NavSelecter) => {
-  const { onVisibleChange, data, value = [], onChecked, title, type = 1 } = props;
-  const [checkedIds, set_checkedIds] = useState<(string | number)[]>([]);
-  const [checkedItems, set_checkedItems] = useState<NavSelecterItemType[]>([]);
-
-  const onChange = (tab: NavSelecterItemType) => {
-    let _checkedIds = [...checkedIds];
-    let _checkedItems = [...checkedItems];
-
-    if (_checkedIds.includes(tab.menuId)) {
-      _checkedIds.splice(checkedIds.indexOf(tab.menuId), 1);
-      _checkedItems.splice(
-        _checkedItems.findIndex((t) => t.menuId == tab.menuId),
-        1,
-      );
-      set_checkedIds([..._checkedIds]);
-      set_checkedItems([..._checkedItems]);
+const NavSelecter: React.FC<NavSelecterProps> = ({ onVisibleChange, data, value = [], onChecked, title, type = 1, resetHandleType = 'default' }) => {
+  const [checkedIds, setCheckedIds] = useState<(string | number)[]>([]);
+
+  const onChange = (tab: NavSelecterData) => {
+    let updatedCheckedIds = [...checkedIds];
+
+    if (updatedCheckedIds.includes(tab.menuId)) {
+      updatedCheckedIds = updatedCheckedIds.filter((id) => id !== tab.menuId);
     } else {
-      if (type == 2 && checkedIds.length + 1 > 6) {
+      if (type === 2 && updatedCheckedIds.length >= 6) {
         message.warn('快速入口最多展示6项');
         return;
       }
-
-      _checkedIds.push(tab.menuId);
-      _checkedItems.push(tab);
-      set_checkedItems([..._checkedItems]);
-      set_checkedIds([..._checkedIds]);
+      updatedCheckedIds.push(tab.menuId);
     }
+
+    setCheckedIds(updatedCheckedIds);
   };
 
-  const onMaskClick = (e: React.MouseEvent) => {
+  const onMaskClick = () => {
     onVisibleChange && onVisibleChange(false);
   };
 
   const onCommit = () => {
-    onChecked && onChecked(checkedItems);
+    if (data) {
+      const selectedItems: any[] = [];
+      data.forEach((tree) => {
+        const nodes = findNodesBySomes(tree, new Set(checkedIds), 'menuId');
+        if (nodes) {
+          nodes.forEach((node) => {
+            selectedItems.push({
+              name: node.name,
+              menuId: node.menuId,
+              path: node.path,
+              type: node.type,
+              contentType: node.contentType,
+              systemId: node.systemId || `${node.menuId}`,
+            });
+          });
+        }
+      });
+      onChecked && onChecked(selectedItems);
+    }
     onVisibleChange && onVisibleChange(false);
   };
 
-  const onReset = () => {
-    set_checkedItems([]);
-    set_checkedIds([]);
+  const onReset = async () => {
+    if (data && resetHandleType != 'default') {
+      const needDelItemIds: string[] = (value as NavSelecterItemType[]).map((a) => a.id);
+      const resp = await removeFastEntrance(needDelItemIds);
+      if (resp) {
+        setCheckedIds([]);
+      }
+    } else {
+      setCheckedIds([]);
+    }
   };
 
-  const checkAllHandle = (e: any) => {
-    let all: any[] = [];
-
-    if (e.target.checked) {
-      data?.forEach((item) => {
-        const _result = getLeafNodes({ child: item.child });
-        all = [...all, ..._result];
+  const checkAllHandle = (e: CheckboxChangeEvent) => {
+    if (e.target.checked && data) {
+      const allIds: (string | number)[] = [];
+      data.forEach((item) => {
+        const leafNodes = getLeafNodes({ child: item.child });
+        leafNodes.forEach((leaf) => allIds.push(leaf.menuId));
       });
-      set_checkedItems([...all]);
-      set_checkedIds(all.map((a) => a.menuId));
+      if (type === 2 && allIds.length > 6) {
+        message.warn('快速入口最多展示6项');
+        return;
+      }
+      setCheckedIds(allIds);
     } else {
-      set_checkedItems([]);
-      set_checkedIds([]);
+      setCheckedIds([]);
     }
   };
 
   useEffect(() => {
-    if (data) {
-      let result: any[] = [];
+    if (resetHandleType == 'request') {
+      setCheckedIds((value as NavSelecterItemType[]).map((a) => a.menuId));
+    } else {
+      setCheckedIds(value as Key[]);
+    }
+  }, [value]);
 
-      for (let tree of data) {
-        const node = findNodesBySomes(tree, new Set(checkedIds), 'menuId');
-        if (node) {
-          result = result.concat([...node]);
+  const getCheckedItems = (): NavSelecterItemType[] => {
+    const selectedItems: NavSelecterItemType[] = [];
+    if (data) {
+      data.forEach((tree) => {
+        const nodes = findNodesBySomes(tree, new Set(checkedIds), 'menuId');
+        if (nodes) {
+          nodes.forEach((node) => {
+            selectedItems.push({
+              id: node.id,
+              name: node.name,
+              menuId: node.menuId,
+              path: node.path,
+              type: node.type,
+              contentType: node.contentType,
+              systemId: node.systemId || `${node.menuId}`,
+            });
+          });
         }
-      }
-      set_checkedItems(result);
+      });
     }
-  }, [checkedIds]);
+    return selectedItems;
+  };
 
-  useEffect(() => {
-    set_checkedIds(value);
-  }, [value]);
+  const checkedItems = getCheckedItems();
 
   return (
-    <div className="navSelecter" onClick={(e) => onMaskClick(e)}>
+    <div className="navSelecter" onClick={onMaskClick}>
       <div className="container">
         <div className="selecterTitle">{title}</div>
-        <div className="closeBtn" onClick={(e) => onMaskClick(e)}>
-          <img src={require('./images/close.png')} />
+        <div className="closeBtn" onClick={onMaskClick}>
+          <img src={require('./images/close.png')} alt="Close" />
         </div>
         <div className="content" onClick={(e) => e.stopPropagation()}>
-          <div>
-            <Tabs defaultActiveKey="1">
-              {data?.map((val) => (
-                <TabPane tab={`${val.name}(${getLeafNodes({ child: val.child }).length})`} key={val.menuId}>
-                  <div className="contentInner">
-                    {val.child?.map((item) => (
-                      <div className="row" key={item.menuId}>
-                        <div className="rowName">{item.name}</div>
-                        <div className="rowWrap">
-                          {item.child?.map((a) => {
-                            if (a.type == 3 || a.type == 1) {
-                              return (
-                                <div className="tab" key={a.menuId}>
-                                  <Checkbox
-                                    onChange={() =>
-                                      onChange({
-                                        name: a.name,
-                                        menuId: a.menuId,
-                                        path: a.path,
-                                        type: a.type,
-                                        contentType: a.contentType,
-                                        systemId: a.systemId,
-                                      })
-                                    }
-                                    checked={checkedIds.includes(a.menuId)}
-                                  ></Checkbox>
-                                  <span style={{ display: 'inline-block', width: '75%', marginLeft: 8, whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }}>{a.name}</span>
-                                </div>
-                              );
-                            }
-                          })}
-                        </div>
+          <Tabs defaultActiveKey="1">
+            {data?.map((val) => (
+              <TabPane tab={`${val.name}(${getLeafNodes({ child: val.child }).length})`} key={val.menuId}>
+                <div className="contentInner">
+                  {val.child?.map((item) => (
+                    <div className="row" key={item.menuId}>
+                      <div className="rowName">{item.name}</div>
+                      <div className="rowWrap">
+                        {item.child?.map((a) => {
+                          if (a.type === 3 || a.type === 1) {
+                            return (
+                              <div className="tab" key={a.menuId}>
+                                <Checkbox
+                                  onChange={() =>
+                                    onChange({
+                                      name: a.name,
+                                      menuId: a.menuId,
+                                      path: a.path,
+                                      type: a.type,
+                                      contentType: a.contentType,
+                                      systemId: a.systemId,
+                                    })
+                                  }
+                                  checked={checkedIds.includes(a.menuId)}
+                                />
+                                <span
+                                  style={{
+                                    display: 'inline-block',
+                                    width: '75%',
+                                    marginLeft: 8,
+                                    whiteSpace: 'nowrap',
+                                    textOverflow: 'ellipsis',
+                                    overflow: 'hidden',
+                                  }}
+                                >
+                                  {a.name}
+                                </span>
+                              </div>
+                            );
+                          }
+                          return null;
+                        })}
                       </div>
-                    ))}
-                  </div>
-                </TabPane>
-              ))}
-            </Tabs>
-          </div>
+                    </div>
+                  ))}
+                </div>
+              </TabPane>
+            ))}
+          </Tabs>
         </div>
         <div className="footer" onClick={(e) => e.stopPropagation()}>
           <span className="count">
-            {type == 1 && <Checkbox onChange={(e) => checkAllHandle(e)}>全部开启 </Checkbox>}
-            {type == 1 && <span className="count">{`已选中${checkedIds.length}项`}</span>}
-            {type == 2 && `当前快速入口已展示${checkedItems.length}/6项`}
+            {type === 1 && <Checkbox onChange={checkAllHandle}>全部开启</Checkbox>}
+            {type === 1 && <span className="count">{`已选中${checkedIds.length}项`}</span>}
+            {type === 2 && `当前快速入口已展示${checkedItems.length}/6项`}
           </span>
           <div className="btnGroup">
             <Button className="resetBtn btn" style={{ marginRight: 8 }} onClick={onReset}>

+ 24 - 0
src/components/emptyPage/index.tsx

@@ -0,0 +1,24 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2025-03-20 14:16:22
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-03-20 14:57:22
+ * @FilePath: /KC-MiddlePlatform/src/components/emptyPage/index.tsx
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+
+import './style.less';
+
+const emptyPage = ({ textContent }: { textContent: string }) => {
+  return (
+    <div className="emptyPage">
+      <div className="emptyPage-title">系统提示</div>
+      <div className="emptyPage-detail">{textContent}</div>
+      <div className="imgWrap">
+        <img src={require('../../../public/images/xitongtishichatu.png')} alt="" />
+      </div>
+    </div>
+  );
+};
+
+export default emptyPage;

+ 98 - 0
src/components/emptyPage/style.less

@@ -0,0 +1,98 @@
+.emptyPage {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  padding: 32px;
+  background: #ffffff;
+  border-radius: 4px;
+  margin: 16px;
+  .emptyPage-title {
+    font-size: 32px;
+    height: 32px;
+    font-weight: bold;
+    color: #17181a;
+    line-height: 32px;
+    margin-bottom: 16px;
+  }
+  .emptyPage-detail {
+    font-weight: 400;
+    font-size: 18px;
+    color: #525866;
+    line-height: 27px;
+  }
+  .imgWrap {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    margin-top: 32px;
+    background: linear-gradient(180deg, #f0f5fd 0%, #ffffff 100%);
+    & > img {
+      width: 70%;
+    }
+  }
+
+  .imgWrap {
+    display: flex;
+    flex: 1;
+    justify-content: center;
+    align-items: center;
+    width: 100%;
+    height: 100%;
+    background-size: cover !important;
+    background-repeat: no-repeat !important;
+
+    & > img {
+      width: 640px;
+      transition: all 0.3s linear;
+    }
+  }
+
+  /* 当屏幕宽度小于或等于 600px */
+  @media screen and (max-width: 1250px) {
+    .imgWrap > img {
+      width: 500px;
+    }
+  }
+
+  /* 当屏幕宽度小于或等于 600px */
+  @media screen and (min-width: 1250px) and (max-width: 1360px) {
+    .imgWrap > img {
+      width: 600px;
+    }
+  }
+
+  /* 当屏幕宽度在 601px 到 900px */
+  @media screen and (min-width: 1360px) and (max-width: 1500px) {
+    .imgWrap > img {
+      width: 640px;
+    }
+  }
+
+  /* 当屏幕宽度大于 900px */
+  @media screen and (min-width: 1500px) and (max-width: 1760px) {
+    .imgWrap > img {
+      width: 760px;
+    }
+  }
+
+  /* 当屏幕宽度大于 900px */
+  @media screen and (min-width: 1760px) and (max-width: 1920px) {
+    .imgWrap > img {
+      width: 800px;
+    }
+  }
+
+  /* 当屏幕宽度大于 900px */
+  @media screen and (min-width: 1960px) and (max-width: 2000px) {
+    .imgWrap > img {
+      width: 900px;
+    }
+  }
+
+  /* 当屏幕宽度大于 900px */
+  @media screen and (min-width: 2000px) {
+    .imgWrap > img {
+      width: 1000px;
+    }
+  }
+}

+ 59 - 0
src/components/intelligenceBot/AutoResizingTextarea/index.tsx

@@ -0,0 +1,59 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2025-03-04 14:35:09
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-03-06 14:03:33
+ * @FilePath: /KC-MiddlePlatform/src/components/intelligenceBot/AutoResizingTextarea.tsx
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+import React, { useRef, useEffect } from 'react';
+
+import './style.less';
+
+interface AutoResizingTextareaProps {
+  value: string;
+  onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
+  placeholder?: string;
+  onKeyPress?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
+}
+
+const AutoResizingTextarea: React.FC<AutoResizingTextareaProps> = ({ value, onChange, placeholder, onKeyPress }) => {
+  const textareaRef = useRef<HTMLTextAreaElement>(null);
+  const maxHeight = 150; // 设定最大高度,例如 150px
+
+  const adjustHeight = () => {
+    if (textareaRef.current) {
+      // 重置高度,便于重新计算
+      textareaRef.current.style.height = '28px';
+      const newHeight = textareaRef.current.scrollHeight;
+      if (newHeight < maxHeight) {
+        textareaRef.current.style.height = newHeight + 'px';
+        textareaRef.current.style.overflowY = 'hidden';
+      } else {
+        textareaRef.current.style.height = maxHeight + 'px';
+        textareaRef.current.style.overflowY = 'auto';
+      }
+    }
+  };
+
+  useEffect(() => {
+    adjustHeight();
+  }, [value]);
+
+  return (
+    <textarea
+      className="auto-resizing-textarea"
+      ref={textareaRef}
+      value={value}
+      onChange={(e) => {
+        onChange(e);
+        adjustHeight();
+      }}
+      onKeyPress={onKeyPress}
+      placeholder={placeholder}
+      style={{ resize: 'none', outline: 'none', border: 'none !important', width: 430, height: 28, background: 'transparent' }} // 禁止手动拖拽调整大小
+    />
+  );
+};
+
+export default AutoResizingTextarea;

+ 23 - 0
src/components/intelligenceBot/AutoResizingTextarea/style.less

@@ -0,0 +1,23 @@
+/* Chrome, Opera, Safari */
+textarea::-webkit-input-placeholder {
+  color: #a3b1cc;
+}
+
+/* Firefox 19+ */
+textarea::-moz-placeholder {
+  color: #a3b1cc;
+}
+
+/* IE 10+ */
+textarea:-ms-input-placeholder {
+  color: #a3b1cc;
+}
+
+/* Firefox 18- */
+textarea:-moz-placeholder {
+  color: #a3b1cc;
+}
+
+.auto-resizing-textarea {
+  border: none !important;
+}

+ 241 - 0
src/components/intelligenceBot/Chat/index.tsx

@@ -0,0 +1,241 @@
+import React, { useEffect } from 'react';
+import './style.less';
+import ReactMarkdown from 'react-markdown';
+import { createFromIconfontCN, LoadingOutlined } from '@ant-design/icons';
+import { Spin, Tooltip } from 'antd';
+import { Column, Line, Pie } from '@ant-design/charts';
+
+export interface Message {
+  id?: string; // 创建消息时赋予唯一 id
+  sender: 'user' | 'bot';
+  thinkContent?: string;
+  content: string; // 可能包含纯文本或 HTML
+  loading?: boolean;
+  duration?: number; // 回答耗时(秒)
+  collapse?: boolean; // 保存折叠状态
+  complete?: boolean;
+  contentType?: string;
+  charts?: {
+    type: 'LineChart' | 'ColumnChart' | 'PieChart';
+    xField: string;
+    yField: string;
+    angleField: string;
+    colorField: string;
+    data: any[];
+  };
+}
+
+const IconFont = createFromIconfontCN({
+  scriptUrl: '', // 请填写图标脚本地址
+});
+
+const antIcon = <LoadingOutlined style={{ fontSize: 14, color: '#17181A' }} spin />;
+
+// 单独封装一个 memoized 的 Spinner 组件,避免频繁重绘导致动画重置
+const SpinnerComponent: React.FC<{ loading: boolean }> = React.memo(({ loading }) => {
+  return loading ? <Spin indicator={antIcon} style={{ paddingRight: 8 }} /> : null;
+});
+
+// 拆分机器人消息中的状态部分(头像旁边那块)
+// 新增属性 hasDeepThinking,根据该属性显示不同的文案
+interface BotMessageStatusProps {
+  loading: boolean;
+  duration?: number;
+  collapse: boolean;
+  onToggleCollapse: () => void;
+  hasDeepThinking: boolean;
+}
+const BotMessageStatus: React.FC<BotMessageStatusProps> = React.memo(
+  ({ loading, duration, collapse, onToggleCollapse, hasDeepThinking }) => {
+    return (
+      <div className="bot-message-status">
+        {loading ? (
+          <>
+            <SpinnerComponent loading={loading} />
+            {hasDeepThinking ? '深度思考中…' : '内容输出中…'}
+          </>
+        ) : (
+          <>
+            {hasDeepThinking ? `已深度思考${duration ? `(用时${duration}秒)` : ''}` : `内容已输出完毕${duration ? `(用时${duration}秒)` : ''}`}
+            {hasDeepThinking && <IconFont type={collapse ? 'iconxiangxia' : 'iconxiangshang'} onClick={onToggleCollapse} style={{ cursor: 'pointer', paddingLeft: 8, fontSize: 16 }} />}
+          </>
+        )}
+      </div>
+    );
+  },
+  (prevProps, nextProps) =>
+    prevProps.loading === nextProps.loading && prevProps.duration === nextProps.duration && prevProps.collapse === nextProps.collapse && prevProps.hasDeepThinking === nextProps.hasDeepThinking,
+);
+
+// 拆分机器人消息中的文本内容部分,同时处理图表展示
+interface BotMessageContentProps {
+  thinkContent?: string;
+  content: string;
+  msg: Message;
+  charts?: {
+    type: string;
+    xField: string;
+    yField: string;
+    angleField: string;
+    colorField: string;
+    data: any[];
+  };
+  // 新增 onShowDetail 回调属性,用于点击详情按钮时触发
+  onShowDetail?: (charts: any, content: string) => void;
+}
+
+const BotMessageContent: React.FC<BotMessageContentProps> = React.memo(({ thinkContent, content, charts, onShowDetail, msg }) => {
+  return (
+    <div className="bot-message-text">
+      <div className="think-text">{thinkContent}</div>
+      <div className="msg-content">
+        {/* 判断是否存在图表数据 */}
+        {charts && charts.data && charts.data.length > 0 && (
+          <div className={msg.content.length > 0 && msg.complete ? 'charts-content canReadDetail' : 'charts-content'}>
+            <div className="charts-detail-btn" onClick={() => onShowDetail && onShowDetail(charts, content)}>
+              <IconFont type={'iconchakan'} style={{ fontSize: 16, paddingRight: 4 }} />
+              详情
+            </div>
+            {charts.type === 'LineChart' && <Line {...charts} />}
+            {charts.type === 'ColumnChart' && <Column {...charts} />}
+            {charts.type === 'PieChart' && <Pie {...charts} />}
+          </div>
+        )}
+        <ReactMarkdown skipHtml={false}>{content}</ReactMarkdown>
+      </div>
+    </div>
+  );
+});
+
+// 整体的机器人消息组件
+interface BotMessageProps {
+  msg: Message;
+  onToggleCollapse: () => void;
+  handleCopy: (text: string) => void;
+  ifCopied: boolean;
+  // 新增 onShowDetail 回调属性
+  onShowDetail?: (charts: any, content: string) => void;
+}
+const BotMessage: React.FC<BotMessageProps> = ({ msg, onToggleCollapse, handleCopy, ifCopied, onShowDetail }) => {
+  const isCollapsed = msg.collapse || false;
+  // 通过判断 thinkContent 是否存在且非空来确定是否存在深度思考内容
+  const hasDeepThinking = !!(msg.thinkContent && msg.thinkContent.trim().length > 0);
+  return (
+    <div className="bot-message-content">
+      <div className="bot-message-avatar">
+        <img src={require('../images/ai-avator.png')} alt="" />
+        <BotMessageStatus loading={msg.loading || false} duration={msg.duration} collapse={isCollapsed} onToggleCollapse={onToggleCollapse} hasDeepThinking={hasDeepThinking} />
+      </div>
+      <BotMessageContent
+        thinkContent={isCollapsed ? '' : msg.thinkContent}
+        content={msg.content}
+        msg={msg}
+        charts={msg.charts} // 传递图表数据
+        onShowDetail={onShowDetail}
+      />
+      {msg.content.length > 0 && msg.complete && (
+        <Tooltip title={ifCopied ? '已复制' : '点击复制'}>
+          <div className="copyBtn" onClick={() => handleCopy(msg.content)}>
+            <IconFont type={'iconfuzhi'} style={{ cursor: 'pointer', fontSize: 16 }} />
+          </div>
+        </Tooltip>
+      )}
+    </div>
+  );
+};
+
+interface ChatProps {
+  messages: Message[];
+  chatContainerRef: React.RefObject<HTMLDivElement>;
+  setMessages: React.Dispatch<React.SetStateAction<Message[]>>;
+  // 新增 onShowDetail 回调属性,用于将详情数据传递到上层组件(例如 AiChat)
+  onShowDetail?: (charts: any, content: string) => void;
+}
+
+const Chat: React.FC<ChatProps> = ({ messages, chatContainerRef, setMessages, onShowDetail }) => {
+  const [ifCopied, setIfCopied] = React.useState<boolean>(false);
+
+  const fallbackCopyTextToClipboard = (text: string) => {
+    const textArea = document.createElement('textarea');
+    textArea.value = text;
+    textArea.style.position = 'fixed';
+    textArea.style.top = '0';
+    textArea.style.left = '0';
+    textArea.style.width = '2em';
+    textArea.style.height = '2em';
+    textArea.style.padding = '0';
+    textArea.style.border = 'none';
+    textArea.style.outline = 'none';
+    textArea.style.boxShadow = 'none';
+    textArea.style.background = 'transparent';
+    document.body.appendChild(textArea);
+    textArea.select();
+    try {
+      const successful = document.execCommand('copy');
+      if (successful) setIfCopied(true);
+    } catch (err) {
+      console.error('复制失败', err);
+    }
+    document.body.removeChild(textArea);
+  };
+
+  const handleCopy = (text: string) => {
+    if (!text) {
+      console.error('未获取到文本内容');
+      return;
+    }
+    if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
+      navigator.clipboard
+        .writeText(text)
+        .then(() => {
+          setIfCopied(true);
+        })
+        .catch((err) => {
+          console.error('使用 Clipboard API 复制失败,尝试备用方案:', err);
+          fallbackCopyTextToClipboard(text);
+        });
+    } else {
+      fallbackCopyTextToClipboard(text);
+    }
+  };
+
+  useEffect(() => {
+    if (ifCopied) {
+      const timer = setTimeout(() => {
+        setIfCopied(false);
+      }, 2000);
+      return () => clearTimeout(timer);
+    }
+  }, [ifCopied]);
+
+  return (
+    <div className="chat-container" ref={chatContainerRef}>
+      {messages.map((msg, idx) => {
+        const key = msg.id || idx;
+        if (msg.sender === 'user') {
+          return (
+            <div key={key} className="chat-message user-message">
+              <div>{msg.content}</div>
+            </div>
+          );
+        } else {
+          return (
+            <div key={key} className="chat-message bot-message">
+              <BotMessage
+                msg={msg}
+                onToggleCollapse={() => {
+                  setMessages((prevMessages) => prevMessages.map((m, i) => (i === idx ? { ...m, collapse: !m.collapse } : m)));
+                }}
+                handleCopy={handleCopy}
+                ifCopied={ifCopied}
+                onShowDetail={onShowDetail}
+              />
+            </div>
+          );
+        }
+      })}
+    </div>
+  );
+};
+
+export default Chat;

+ 113 - 0
src/components/intelligenceBot/Chat/style.less

@@ -0,0 +1,113 @@
+.chat-container {
+  display: flex;
+  flex: 1;
+  width: 100%;
+  flex-direction: column;
+  overflow-y: auto;
+  padding: 16px;
+  padding-bottom: 0;
+
+  .chat-message {
+    padding: 10px;
+    border-radius: 5px;
+    max-width: 100%;
+    margin-bottom: 16px;
+    word-wrap: break-word; // 自动换行
+
+    &.user-message {
+      max-width: 93.5%;
+      padding: 9px 12px;
+      align-self: flex-end;
+      background-color: #e6f2ff;
+    }
+    &.bot-message {
+      align-self: flex-start;
+      padding: 0;
+
+      .bot-message-content {
+        .bot-message-avatar {
+          display: flex;
+          flex-direction: row;
+          justify-content: flex-start;
+          align-items: center;
+          margin-bottom: 8px;
+
+          & > img {
+            width: 24px;
+            height: 24px;
+            margin-right: 8px;
+          }
+          .bot-message-status {
+            display: flex;
+            flex-direction: row;
+            justify-content: flex-start;
+            align-items: center;
+            height: 32px;
+            padding: 12px;
+            background: #f5f7fa;
+            border-radius: 8px;
+            font-weight: 400;
+            font-size: 12px;
+            color: #3d424d;
+          }
+        }
+        .bot-message-text {
+          .think-text {
+            font-weight: 300;
+            font-size: 12px;
+            color: #7a8599;
+            line-height: 16px;
+            padding-left: 32px;
+            padding-right: 8px;
+            text-align: justify;
+            font-style: normal;
+            margin-bottom: 12px;
+            overflow: hidden;
+          }
+          .msg-content {
+            padding: 0 8px;
+            padding-left: 20px;
+
+            .charts-content {
+              position: relative;
+              width: 460px;
+              height: 200px;
+              margin-top: 24px;
+              margin-bottom: 24px;
+              .charts-detail-btn {
+                display: none;
+                flex-direction: row;
+                justify-content: center;
+                align-items: center;
+                cursor: pointer;
+                position: absolute;
+                right: 0;
+                top: -20px;
+                width: 60px;
+                height: 24px;
+                z-index: 99;
+                background: rgba(41, 44, 51, 0.7);
+                border-radius: 4px;
+                color: #fff;
+                font-size: 12px;
+              }
+              &.canReadDetail {
+                &:hover {
+                  .charts-detail-btn {
+                    display: flex;
+                  }
+                }
+              }
+            }
+          }
+        }
+        .copyBtn {
+          display: inline-block;
+          width: 24px;
+          height: 24px;
+          margin-left: 20px;
+        }
+      }
+    }
+  }
+}

BIN=BIN
src/components/intelligenceBot/images/ai-avator.png


BIN=BIN
src/components/intelligenceBot/images/ai-eyes.png


BIN=BIN
src/components/intelligenceBot/images/ai.png


BIN=BIN
src/components/intelligenceBot/images/ai_descr.png


BIN=BIN
src/components/intelligenceBot/images/send_white.png


+ 369 - 0
src/components/intelligenceBot/index.tsx

@@ -0,0 +1,369 @@
+import React, { useEffect, useRef, useState } from 'react';
+import { Drawer } from 'antd';
+import './style.less';
+import Chat, { Message } from './Chat';
+import { createFromIconfontCN } from '@ant-design/icons';
+import AutoResizingTextarea from './AutoResizingTextarea/index';
+import ReactMarkdown from 'react-markdown';
+import { Line, Column, Pie } from '@ant-design/charts';
+
+const IconFont = createFromIconfontCN({
+  scriptUrl: '', // 请填写图标脚本地址
+});
+
+const AiChat: React.FC = () => {
+  const [visible, setVisible] = useState(false);
+  const [messages, setMessages] = useState<Message[]>([]);
+  const [inputValue, setInputValue] = useState<string>('');
+  const [isProcessing, setIsProcessing] = useState<boolean>(false);
+  const chatContainerRef = useRef<HTMLDivElement>(null);
+
+  const [detailData, setDetailData] = useState<{ charts: any; content: string } | null>(null);
+
+  const openDrawer = () => setVisible(true);
+  const closeDrawer = () => setVisible(false);
+
+  /**
+   * 保持聊天容器滚动到底部
+   */
+  const scrollToBottom = () => {
+    if (chatContainerRef.current) {
+      chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight;
+    }
+  };
+
+  /**
+   * 发送消息的主函数
+   */
+  const handleSendMessage = async () => {
+    if (isProcessing) return;
+
+    const question = inputValue.trim();
+    if (!question) return;
+
+    const startTime = Date.now();
+    let durationRecorded = false;
+    let hasThinkTag = false;
+
+    // 添加用户消息
+    const userMsg: Message = { sender: 'user', content: question };
+    setMessages((prev) => [...prev, userMsg]);
+    setInputValue('');
+
+    // 添加占位的机器人消息
+    const botPlaceholder: Message = { sender: 'bot', content: '', loading: true };
+    setMessages((prev) => [...prev, botPlaceholder]);
+
+    setIsProcessing(true);
+
+    try {
+      const response = await fetch('http://116.62.47.88:5003/ask', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({ question: question }),
+      });
+
+      if (!response.body) {
+        throw new Error('No response body');
+      }
+
+      const reader = response.body.getReader();
+      const decoder = new TextDecoder();
+      let buffer = '';
+      let isThinking = false;
+
+      while (true) {
+        const { done, value } = await reader.read();
+        if (done) break;
+
+        const chunk = decoder.decode(value, { stream: true });
+        buffer += chunk;
+        const parts = buffer.split('\n\n');
+
+        for (let i = 0; i < parts.length - 1; i++) {
+          const part = parts[i].trim();
+          if (!part) continue;
+
+          try {
+            const parsed = JSON.parse(part);
+            const {
+              response: { chatText, charts, charts_done },
+            } = parsed;
+
+            if (part.indexOf('<think>') > -1) {
+              isThinking = true;
+              hasThinkTag = true;
+            }
+            if (part.indexOf('</think>') > -1) {
+              isThinking = false;
+              if (!durationRecorded) {
+                const endTime = Date.now();
+                const duration = Math.floor((endTime - startTime) / 1000);
+                setMessages((prev) => {
+                  const updated = [...prev];
+                  const lastIndex = updated.length - 1;
+                  if (lastIndex >= 0 && updated[lastIndex].sender === 'bot') {
+                    updated[lastIndex].duration = duration;
+                    updated[lastIndex].loading = false;
+                  }
+                  return updated;
+                });
+                durationRecorded = true;
+              }
+            }
+
+            const processedStr = chatText.replace(/<think>/g, '').replace(/<\/think>/g, '');
+
+            setMessages((prev) => {
+              const updated = [...prev];
+              const lastIndex = updated.length - 1;
+              if (lastIndex >= 0 && updated[lastIndex].sender === 'bot') {
+                if (isThinking) {
+                  updated[lastIndex].thinkContent = (updated[lastIndex].thinkContent || '') + processedStr;
+                } else {
+                  updated[lastIndex].content += processedStr;
+                }
+              }
+              return updated;
+            });
+
+            if (charts && Object.keys(charts).length > 0 && charts_done) {
+              setMessages((prev) => {
+                const updated = [...prev];
+                const lastIndex = updated.length - 1;
+                if (lastIndex >= 0 && updated[lastIndex].sender === 'bot') {
+                  updated[lastIndex].charts = charts;
+                }
+                return updated;
+              });
+            }
+          } catch (e) {
+            console.error('解析JSON失败:', e, '部分内容:', part);
+          }
+        }
+
+        buffer = parts[parts.length - 1];
+        scrollToBottom();
+      }
+
+      if (buffer.trim()) {
+        try {
+          const parsed = JSON.parse(buffer);
+          const {
+            response: { chatText, charts, charts_done },
+          } = parsed;
+          const processedStr = chatText.replace(/<think>/g, '').replace(/<\/think>/g, '');
+          setMessages((prev) => {
+            const updated = [...prev];
+            const lastIndex = updated.length - 1;
+            if (lastIndex >= 0 && updated[lastIndex].sender === 'bot') {
+              updated[lastIndex].content += processedStr;
+            }
+            return updated;
+          });
+          if (charts && Object.keys(charts).length > 0 && charts_done) {
+            setMessages((prev) => {
+              const updated = [...prev];
+              const lastIndex = updated.length - 1;
+              if (lastIndex >= 0 && updated[lastIndex].sender === 'bot') {
+                updated[lastIndex].charts = charts;
+              }
+              return updated;
+            });
+          }
+        } catch (e) {
+          console.error('解析剩余JSON失败:', e, buffer);
+        }
+      }
+
+      setMessages((prev) => {
+        const updated = [...prev];
+        const lastIndex = updated.length - 1;
+        if (lastIndex >= 0 && updated[lastIndex].sender === 'bot') {
+          updated[lastIndex].complete = true;
+        }
+        return updated;
+      });
+    } catch (error) {
+      console.error('请求错误:', error);
+      setMessages((prev) => {
+        const updated = [...prev];
+        const lastIndex = updated.length - 1;
+        if (lastIndex >= 0 && updated[lastIndex].sender === 'bot') {
+          updated[lastIndex] = {
+            sender: 'bot',
+            content: '请求失败,请稍后重试。',
+            loading: false,
+          };
+        }
+        return updated;
+      });
+    } finally {
+      if (!durationRecorded) {
+        const endTime = Date.now();
+        const duration = Math.floor((endTime - startTime) / 1000);
+        setMessages((prev) => {
+          const updated = [...prev];
+          const lastIndex = updated.length - 1;
+          if (lastIndex >= 0 && updated[lastIndex].sender === 'bot') {
+            updated[lastIndex].duration = duration;
+            updated[lastIndex].loading = false;
+          }
+          return updated;
+        });
+        durationRecorded = true;
+      }
+      setIsProcessing(false);
+    }
+  };
+
+  const handleKeyPress = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
+    if (e.key === 'Enter' && !e.shiftKey) {
+      e.preventDefault();
+      handleSendMessage();
+    }
+  };
+
+  useEffect(() => {
+    scrollToBottom();
+  }, [messages]);
+
+  useEffect(() => {
+    if (!visible) {
+      setMessages([]);
+      setInputValue('');
+      setDetailData(null);
+    }
+  }, [visible]);
+
+  // 点击详情按钮时的回调,将图表数据和内容保存到 detailData 中
+  const handleShowDetail = (charts: any, content: string) => {
+    setDetailData({ charts, content });
+  };
+
+  // 辅助复制文本的函数
+  const fallbackCopyTextToClipboard = (text: string) => {
+    const textArea = document.createElement('textarea');
+    textArea.value = text;
+    textArea.style.position = 'fixed';
+    textArea.style.top = '0';
+    textArea.style.left = '0';
+    textArea.style.width = '2em';
+    textArea.style.height = '2em';
+    textArea.style.padding = '0';
+    textArea.style.border = 'none';
+    textArea.style.outline = 'none';
+    textArea.style.boxShadow = 'none';
+    textArea.style.background = 'transparent';
+    document.body.appendChild(textArea);
+    textArea.select();
+    try {
+      const successful = document.execCommand('copy');
+      if (successful) console.log('复制成功');
+    } catch (err) {
+      console.error('复制失败', err);
+    }
+    document.body.removeChild(textArea);
+  };
+
+  const handleCopy = (text: string) => {
+    if (!text) {
+      console.error('未获取到文本内容');
+      return;
+    }
+    if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
+      navigator.clipboard
+        .writeText(text)
+        .then(() => {
+          console.log('复制成功');
+        })
+        .catch((err) => {
+          console.error('使用 Clipboard API 复制失败,尝试备用方案:', err);
+          fallbackCopyTextToClipboard(text);
+        });
+    } else {
+      fallbackCopyTextToClipboard(text);
+    }
+  };
+
+  return (
+    <>
+      {/* 悬浮按钮 */}
+      <div className="ai-chat-drawer-button" onClick={openDrawer}>
+        <img src={require('./images/ai.png')} alt="" />
+      </div>
+
+      {/* 抽屉组件 */}
+      <Drawer
+        className={detailData ? 'ai-chat-drawer hasDetail' : 'ai-chat-drawer'}
+        headerStyle={{ display: 'none' }}
+        bodyStyle={{ background: 'rgba(240,246,252,0.98)', padding: 8 }}
+        placement="right"
+        onClose={closeDrawer}
+        open={visible}
+        destroyOnClose
+        width={detailData ? '90%' : 560}
+        mask={false}
+      >
+        <div className="chat-header">
+          <div className="chat-header-title">
+            <img src={require('./images/ai-eyes.png')} alt="" />
+            <span>智能小K</span>
+          </div>
+          <div className="closeBtn" onClick={closeDrawer}>
+            <IconFont style={{ fontSize: 16 }} type={'iconquxiao'} />
+          </div>
+        </div>
+        <div className="chat-content-wrap">
+          <div className="chat-content">
+            {messages.length === 0 && <img src={require('./images/ai_descr.png')} alt="" />}
+            {messages.length > 0 && <Chat messages={messages} chatContainerRef={chatContainerRef} setMessages={setMessages} onShowDetail={handleShowDetail} />}
+            <div className={messages.length > 0 ? 'startInput large' : 'startInput'}>
+              <AutoResizingTextarea value={inputValue} onChange={(e) => setInputValue(e.target.value)} onKeyPress={handleKeyPress} placeholder="请输入您的问题..." />
+              <div className={`chat-sendBtn ${isProcessing ? 'isProcessing' : ''} ${inputValue.length > 0 ? 'hasText' : ''}`} onClick={handleSendMessage}>
+                <img src={require('./images/send_white.png')} alt="" />
+              </div>
+            </div>
+          </div>
+          {/* 详情展示区域 */}
+          {detailData && (
+            <div className="chat-detail">
+              <div className="detail-view">
+                <div className="detail-header">
+                  <span className="goback" onClick={() => setDetailData(null)}>
+                    <IconFont type={'iconxiangzuo'} style={{ fontSize: 16 }} />
+                    返回
+                  </span>
+                  <span className="favorite">
+                    <IconFont type={'iconshoucang'} style={{ fontSize: 16 }} /> 收藏
+                  </span>
+                </div>
+                <div className="detail-body">
+                  <div className="charts">
+                    {detailData.charts &&
+                      detailData.charts.data &&
+                      detailData.charts.data.length > 0 &&
+                      (detailData.charts.type === 'LineChart' ? (
+                        <Line {...detailData.charts} />
+                      ) : detailData.charts.type === 'ColumnChart' ? (
+                        <Column {...detailData.charts} />
+                      ) : detailData.charts.type === 'PieChart' ? (
+                        <Pie {...detailData.charts} />
+                      ) : null)}
+                  </div>
+
+                  <div style={{ height: 16 }}></div>
+                  <ReactMarkdown skipHtml={false}>{detailData.content}</ReactMarkdown>
+                </div>
+              </div>
+            </div>
+          )}
+        </div>
+      </Drawer>
+    </>
+  );
+};
+
+export default AiChat;

+ 218 - 0
src/components/intelligenceBot/style.less

@@ -0,0 +1,218 @@
+.ai-chat-drawer {
+  .kcmp-ant-drawer-content-wrapper {
+    top: 48px;
+    // min-height: 1280px;
+    height: calc(100vh - 48px);
+    background: rgba(240, 246, 252, 0.98);
+    box-shadow: -8px 0px 16px 0px rgba(64, 85, 128, 0.2);
+    border: 1px solid #ffffff;
+    .kcmp-ant-drawer-content {
+      background: transparent;
+    }
+    .kcmp-ant-drawer-body {
+      height: 100%;
+      .chat-header {
+        display: flex;
+        flex: 1;
+        padding: 0 8px;
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: center;
+        margin-bottom: 9px;
+
+        .chat-header-title {
+          display: flex;
+          flex: 1;
+          flex-direction: row;
+          justify-content: flex-start;
+          align-items: center;
+          & > img {
+            width: 40px;
+            height: 30px;
+          }
+          & > span {
+            font-weight: 500;
+            font-size: 16px;
+            color: #17181a;
+            padding-left: 8px;
+          }
+        }
+        .closeBtn {
+          cursor: pointer;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          width: 24px;
+          height: 24px;
+          border-radius: 4px;
+          &:hover {
+            background: #e6eaf2;
+          }
+        }
+      }
+      .chat-content-wrap {
+        display: flex;
+        height: calc(100% - 40px);
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: center;
+        flex-wrap: nowrap;
+        .chat-content {
+          display: flex;
+          width: 544px;
+          flex-direction: column;
+          justify-content: center;
+          align-items: center;
+          height: 100%;
+          background: #ffffff;
+          border-radius: 8px;
+          & > img {
+            width: 480px;
+            height: 200px;
+          }
+
+          .startInput {
+            display: flex;
+            flex-direction: row;
+            justify-content: flex-start;
+            align-items: flex-end;
+            width: 480px;
+            min-height: 56px;
+            padding: 12px;
+            margin-top: 16px;
+            margin-bottom: 16px;
+            background: #fcfeff;
+            border-radius: 16px;
+            border: 1px solid #dae6f2;
+
+            .chat-sendBtn {
+              display: flex;
+              cursor: pointer;
+              justify-content: center;
+              align-items: center;
+              width: 32px;
+              height: 32px;
+              margin-left: 22px;
+              background: #cfd6e6;
+              border-radius: 8px;
+              // transition: all 0.5s ease;
+              img {
+                width: 24px;
+                height: 24px;
+              }
+
+              &.hasText {
+                background: #3377ff;
+                &:hover {
+                  background: #2e6be6;
+                }
+              }
+
+              &.isProcessing {
+                background: #3377ff;
+                border-radius: 8px;
+                & > img {
+                  display: none;
+                }
+                &::after {
+                  display: block;
+                  content: '';
+                  width: 12px;
+                  height: 12px;
+                  background: #ffffff;
+                  border-radius: 3px;
+                }
+
+                &:hover {
+                  background: #2e6be6;
+                }
+              }
+            }
+
+            &.large {
+              width: 512px;
+            }
+          }
+        }
+        .chat-detail {
+          display: flex;
+          flex: 1;
+          width: 100%;
+          height: 100%;
+          background: #ffffff;
+          border-radius: 8px;
+          margin-left: 8px;
+          padding: 16px;
+          .detail-view {
+            width: 100%;
+            height: 100%;
+            .detail-header {
+              display: flex;
+              width: 100%;
+              height: 32px;
+              flex-direction: row;
+              justify-content: space-between;
+              align-items: center;
+              margin-bottom: 16px;
+              .goback,
+              .favorite {
+                cursor: pointer;
+                text-align: center;
+                width: 64px;
+                height: 32px;
+                line-height: 32px;
+                background: #ffffff;
+                border-radius: 4px;
+                font-weight: 400;
+                font-size: 14px;
+                color: #17181a;
+
+                &:hover {
+                  background: #e6eaf2;
+                }
+              }
+            }
+            .detail-body {
+              // display: flex;
+              // flex: 1;
+              // flex-direction: column;
+              width: 100%;
+              height: calc(100% - 32px);
+              padding: 8px;
+              padding-left: 18px;
+              overflow-y: scroll;
+              .charts {
+                min-width: 640px;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  &.hasDetail {
+    min-width: 1280px;
+  }
+}
+
+.ai-chat-drawer-button {
+  position: fixed;
+  bottom: 20px;
+  cursor: pointer;
+  right: 20px;
+  width: 50px;
+  height: 50px;
+  z-index: 1000;
+  box-shadow: 0px 10px 20px 0px rgba(77, 102, 153, 0.3);
+  border-radius: 12px;
+  transition: all 0.5s ease;
+  & > img {
+    width: 100%;
+    height: 100%;
+  }
+
+  &:hover {
+    box-shadow: 0px 10px 20px 0px rgba(107, 122, 153, 0.6);
+  }
+}

BIN=BIN
src/components/topBar/images/groupIcon.png


+ 234 - 191
src/components/topBar/index.tsx

@@ -1,16 +1,15 @@
 /*
  * @Author: your name
  * @Date: 2021-11-16 09:12:37
- * @LastEditTime: 2024-12-03 14:54:16
+ * @LastEditTime: 2025-01-20 10:38:30
  * @LastEditors: code4eat awesomedema@gmail.com
  * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  * @FilePath: /KC-MiddlePlatform/src/pages/index/components/topBar/index.tsx
  */
 
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
 
 import './style.less';
-
 import { Input, Modal, Tooltip } from 'antd';
 import { LogoutOutlined, SettingOutlined } from '@ant-design/icons';
 
@@ -21,7 +20,7 @@ import { history, useLocation, useModel } from 'umi';
 import { Divider } from 'antd';
 import { updateTokenReq } from '@/service/user';
 import { logoutHandle } from '@/global';
-
+import { switchOrgReq } from '@/service';
 
 interface TopBarType {
   onTabChange?: (data: TopBar.Tab[]) => void; //当tab切换时回调
@@ -30,58 +29,59 @@ interface TopBarType {
   userPannelTabClick?: (tag: 'SETTING' | 'LOGOUT' | 'SETUSERINFO') => void;
   onCloseTab?: (data: TopBar.Tab) => void;
   onTabClick?: (data: TopBar.Tab) => void;
-  userData?: { name: string;[key: string]: any };
+  userData?: { name: string; [key: string]: any };
   navData: TopBar.PanelData[];
-  logo?:string;
-  topBarTitle?:string;
+  logo?: string;
+  topBarTitle?: string;
 }
 
+let isClickInside = false;
 
 const TopBar: React.FC<TopBarType> = (props) => {
-  const { onTabChange, userPannelTabClick, onCloseTab, onTabClick, userData, navData, currentTab,logo=undefined,topBarTitle='欢迎进入医管平台' } = props;
+  const { onTabChange, userPannelTabClick, onCloseTab, onTabClick, userData, navData, currentTab, logo = undefined, topBarTitle = '欢迎进入医管平台' } = props;
   const [systemTabs, setSystemTabs] = useState<TopBar.Tab[]>([]); //已打开的tab
   const [currentSelectedTab, setCurrentSelectedTab] = useState<TopBar.Tab>();
   const [ifOpenPannel, setIfOpenPannel] = useState(false);
   const [arrowRotate, setArrowRotate] = useState(false);
-  const [pageTitle, set_pageTitle] = useState('');
+  // const [pageTitle, set_pageTitle] = useState('');
   const [currentActivedBlockIndex, set_currentActivedBlockIndex] = useState(0);
   const [panelData, set_panelData] = useState<TopBar.PanelData[]>([]);
 
   const [onTabSystemTabs, set_onTabSystemTabs] = useState<TopBar.Tab[]>([]); //tab导航可以放下的数量,剩余通过下拉获取
-  const [onTabSystemTabs_hide, set_onTabSystemTabs_hide] = useState<TopBar.Tab[]>([]);  //下拉掩藏的导航
+  const [onTabSystemTabs_hide, set_onTabSystemTabs_hide] = useState<TopBar.Tab[]>([]); //下拉掩藏的导航
 
   const { initialState, setInitialState } = useModel('@@initialState');
-  const [tokenUpdateModalVisible,set_tokenUpdateModalVisible] = useState(false);
+  const [tokenUpdateModalVisible, set_tokenUpdateModalVisible] = useState(false);
   const location = useLocation();
 
   const [showMoreTabPannel, set_showMoreTabPannel] = useState(false);
-  const [password,set_password] = useState<string|undefined>(undefined);
-
+  const [password, set_password] = useState<string | undefined>(undefined);
+  const [showGroupList, set_showGroupList] = useState(false);
+  const [groupList, set_groupList] = useState<any[]>([]);
+  const [currentActivedGroup, set_currentActivedGroup] = useState<any>(undefined);
 
   const localSavedTab = localStorage.getItem('currentSelectedTab');
 
   const currentSelectedTabFromLocal = localSavedTab ? JSON.parse(localSavedTab) : {};
 
-
+  const PannelRef = useRef<any>(null);
+  const GroupListWrapperRef = useRef<any>(null);
 
   const _systemTabClickHandle = (item: TopBar.Tab) => {
-
     //导航栏tab点击
     // console.log('_systemTabClickHandle',item);
     onTabClick && onTabClick(item);
-    if(item.type != 1&&item.contentType != 7){
+    if (item.type != 1 && item.contentType != 7) {
       localStorage.setItem('currentSelectedTab', JSON.stringify(item));
       setCurrentSelectedTab(item);
     }
   };
 
-
-
   const _systemListClickHandle = (data: TopBar.Tab, currentActivedBlockIndex: number, index: number, i: number) => {
     //导航栏系统菜单列表点击回调
- 
+
     if (currentSelectedTab?.menuId == data.menuId) return;
-    
+
     //临时保存衣打开过的菜单
     const t = localStorage.getItem('visitedTabs');
 
@@ -98,7 +98,8 @@ const TopBar: React.FC<TopBarType> = (props) => {
 
     _systemTabClickHandle(data); //触发一次tab点击
 
-    set_pageTitle(panelData[currentActivedBlockIndex].child[index].name)
+    // set_pageTitle(panelData[currentActivedBlockIndex].child[index].name);
+    setInitialState((s: any) => ({ ...s, pageTitle: panelData[currentActivedBlockIndex].child[index].name }));
 
     if (panelData[currentActivedBlockIndex].child[index].child) {
       //console.log([...panelData[currentActivedBlockIndex].child[index].child])
@@ -129,19 +130,19 @@ const TopBar: React.FC<TopBarType> = (props) => {
     if (delIndex != 0) {
       _systemTabClickHandle(_systemTabs[delIndex - 1]); //自动切换为前一个tab
     }
-    
+
     onTabChange && onTabChange(filtered);
     onCloseTab && onCloseTab(item);
   };
 
   const UserPannel = () => {
     return (
-      <div className='userPannel'>
-        <div className='userPannelTab' onClick={() => _userPannelTabClick('SETTING')}>
+      <div className="userPannel">
+        <div className="userPannelTab" onClick={() => _userPannelTabClick('SETTING')}>
           <SettingOutlined />
           <span>设置</span>
         </div>
-        <div className='userPannelTab' onClick={() => _userPannelTabClick('LOGOUT')}>
+        <div className="userPannelTab" onClick={() => _userPannelTabClick('LOGOUT')}>
           <LogoutOutlined />
           <span>退出</span>
         </div>
@@ -151,24 +152,23 @@ const TopBar: React.FC<TopBarType> = (props) => {
 
   const goChannelIndex = (menuData: any) => {
     setIfOpenPannel(false);
-    onTabClick && onTabClick(menuData)
-  }
+    onTabClick && onTabClick(menuData);
+  };
 
   const goToHome = () => {
-
     const go = () => {
       history.replace('/index');
       setSystemTabs([]); //清空tab导航
       onTabChange && onTabChange([]);
       setCurrentSelectedTab(undefined);
-      set_pageTitle(topBarTitle);
+      setInitialState((s: any) => ({ ...s, pageTitle: topBarTitle }));
       setIfOpenPannel(false);
       console.log('goHome');
       localStorage.removeItem('currentSelectedTab');
       localStorage.removeItem('selectedKeys');
       // localStorage.removeItem('visitedTabs');
       localStorage.removeItem('openKeys');
-    }
+    };
 
     const currentSelectedSubHop_json = localStorage.getItem('currentSelectedSubHop');
     if (currentSelectedSubHop_json) {
@@ -181,47 +181,41 @@ const TopBar: React.FC<TopBarType> = (props) => {
     } else {
       go();
     }
-
-  }
+  };
 
   const goSystemIndex = (name: string) => {
     if (panelData[currentActivedBlockIndex]) {
-      const result: TopBar.TypeBlock[] = panelData[currentActivedBlockIndex].child.filter(t => (t.name == name));
+      const result: TopBar.TypeBlock[] = panelData[currentActivedBlockIndex].child.filter((t) => t.name == name);
       if (result.length > 0) {
-        _systemTabClickHandle(Object.assign(result[0]))
+        _systemTabClickHandle(Object.assign(result[0]));
         // history.push(result[0].path)
       }
     }
-  }
+  };
 
   const openNav = () => {
+    isClickInside = true;
     setIfOpenPannel(!ifOpenPannel);
-  }
-
+  };
 
   const moreItemClickHandle = (systemData: TopBar.Tab) => {
     //点击更多应用时
     _systemTabClickHandle(systemData);
     const temp = onTabSystemTabs[onTabSystemTabs.length - 1];
     const _onTabSystemTabs = [...onTabSystemTabs];
-    const b = _onTabSystemTabs.filter(a => (a.systemId?a.systemId:a.menuId) != (temp.systemId?temp.systemId:temp.menuId));
-  
-    set_onTabSystemTabs([...b, systemData]);
-    set_onTabSystemTabs_hide([...onTabSystemTabs_hide.filter(a => (a.systemId?a.systemId:a.menuId) != (systemData.systemId?systemData.systemId:systemData.menuId)), temp])
-  }
-
+    const b = _onTabSystemTabs.filter((a) => (a.systemId ? a.systemId : a.menuId) != (temp.systemId ? temp.systemId : temp.menuId));
 
+    set_onTabSystemTabs([...b, systemData]);
+    set_onTabSystemTabs_hide([...onTabSystemTabs_hide.filter((a) => (a.systemId ? a.systemId : a.menuId) != (systemData.systemId ? systemData.systemId : systemData.menuId)), temp]);
+  };
 
   const reSetNav = (_panelData: TopBar.PanelData[], cur: TopBar.Tab) => {
-
-    if (!(_panelData.length > 0)) return
+    if (!(_panelData.length > 0)) return;
 
     if (JSON.stringify(cur) != '{}') {
-
       let blockIndex = 0;
       let channelIndex = 0;
 
-
       const _currentSelectedTabFromLocal = cur;
 
       one: for (let index = 0; index < _panelData.length; index++) {
@@ -235,7 +229,8 @@ const TopBar: React.FC<TopBarType> = (props) => {
                 for (let k = 0; k < _systems.length; k++) {
                   if (_systems[k].menuId == _currentSelectedTabFromLocal.menuId) {
                     set_currentActivedBlockIndex(blockIndex);
-                    set_pageTitle(_panelData[blockIndex].child[channelIndex].name); //设置体系标题
+                    // set_pageTitle(_panelData[blockIndex].child[channelIndex].name); //设置体系标题
+                    setInitialState((s: any) => ({ ...s, pageTitle: _panelData[blockIndex].child[channelIndex].name }));
 
                     break one;
                   }
@@ -247,23 +242,21 @@ const TopBar: React.FC<TopBarType> = (props) => {
       }
 
       if (_panelData && _panelData.length > 0 && _panelData[blockIndex].child) {
-        setSystemTabs(_panelData[blockIndex].child[channelIndex].child);  //恢复体系列表
+        setSystemTabs(_panelData[blockIndex].child[channelIndex].child); //恢复体系列表
         setCurrentSelectedTab(_currentSelectedTabFromLocal);
         localStorage.setItem('currentSelectedTab', JSON.stringify(_currentSelectedTabFromLocal));
-        setInitialState((s) => ({ ...s, currentSelectedSys: _currentSelectedTabFromLocal }) as any);
+        setInitialState((s) => ({ ...s, currentSelectedSys: _currentSelectedTabFromLocal } as any));
       }
 
       // console.log({_currentSelectedTabFromLocal,location});
-      const {pathname} = location;
-      if(pathname.indexOf(_currentSelectedTabFromLocal.path) == -1){
+      const { pathname } = location;
+      if (pathname.indexOf(_currentSelectedTabFromLocal.path) == -1) {
         history.push(_currentSelectedTabFromLocal.path);
       }
 
       //_systemTabClickHandle(_currentSelectedTabFromLocal);  //恢复选中的tab
-
-
     }
-  }
+  };
 
   const [hideTimer, setHideTimer] = useState<null | any>(null);
   const handleTabMoreMouseLeave = () => {
@@ -287,21 +280,33 @@ const TopBar: React.FC<TopBarType> = (props) => {
   };
 
   const updateToken = async () => {
-        const account = (localStorage.getItem('account')) as string;
-        const hospSign = localStorage.getItem('hospSign');
-        const data = {
-          account,
-          password:password,
-          hospSign
-        }
-        const resp = await updateTokenReq(data);
-        if(resp){
-          set_password(undefined);
-          set_tokenUpdateModalVisible(false);
-          window.location.reload();
-        }
-  }
+    const account = localStorage.getItem('account') as string;
+    const hospSign = localStorage.getItem('hospSign');
+    const data = {
+      account,
+      password: password,
+      hospSign,
+    };
+    const resp = await updateTokenReq(data);
+    if (resp) {
+      set_password(undefined);
+      set_tokenUpdateModalVisible(false);
+      window.location.reload();
+    }
+  };
 
+  const switchGroupHandle = async (group: any) => {
+    const resp = await switchOrgReq(group.orgId, group.orgName);
+    if (resp) {
+      set_currentActivedGroup(group);
+      set_showGroupList(false);
+      const currentPath = window.location.pathname;
+      window.history.pushState(null, '', '/');
+      setTimeout(() => {
+        window.history.pushState(null, '', currentPath);
+      }, 100);
+    }
+  };
 
   useEffect(() => {
     if (currentSelectedTabFromLocal) {
@@ -309,25 +314,17 @@ const TopBar: React.FC<TopBarType> = (props) => {
     }
 
     set_panelData(navData);
-
-  }, [navData])
+  }, [navData]);
 
   useEffect(() => {
-
     if (currentTab) reSetNav(panelData, currentTab);
-
   }, [currentTab]);
 
-
-  useEffect(()=>{
-     set_pageTitle(topBarTitle);
-  },[topBarTitle])
-
-
-
-
   useEffect(() => {
+    setInitialState((s: any) => ({ ...s, pageTitle: topBarTitle }));
+  }, [topBarTitle]);
 
+  useEffect(() => {
     if (systemTabs.length > 5) {
       //set_onTabSystemTabs(tabs);
       let _onTabSystemTabs: any[] = [];
@@ -353,25 +350,36 @@ const TopBar: React.FC<TopBarType> = (props) => {
     }
   }, [systemTabs]);
 
+  useEffect(() => {
+    if (initialState?.userInfo) {
+      const { orgInfo = [] } = initialState.userInfo;
+      if (orgInfo && orgInfo.length > 0) {
+        const defaultGroup = orgInfo.filter((a: any) => a.defaultFlag);
+        set_groupList(orgInfo);
+        set_currentActivedGroup(defaultGroup.length > 0 ? defaultGroup[0] : undefined);
+      }
+    } else {
+      console.error('initialState or userInfo is null/undefined');
+    }
+  }, [initialState]);
 
   useEffect(() => {
+    // _systemTabClickHandle(currentSelectedTabFromLocal);  //恢复选中的tab
 
-    //_systemTabClickHandle(currentSelectedTabFromLocal);  //恢复选中的tab
+    const handleClickOutside = (event: { target: any }) => {
+      if (!isClickInside && PannelRef.current && !PannelRef.current.contains(event.target)) {
+        setIfOpenPannel(false);
+      }
+      if (!isClickInside && GroupListWrapperRef.current && !GroupListWrapperRef.current.contains(event.target)) {
+        set_showGroupList(false);
+      }
+      isClickInside = false;
+    };
 
-    document.body.addEventListener('click', (e: any) => {
+    document.addEventListener('click', handleClickOutside);
 
-      const classes = ['panel', 'typeBlockName', 'left', 'typeBlockIcon', 'typeBlock', 'typeBlock active', 'active', 'right', 'row', 'rowDetai', 'channelName', 'channelList', 'systemTab', 'channelIcon', 'rowDetail','typeBlockIcon typeBlockIcon1','typeBlockIcon typeBlockIcon2','typeBlockIcon typeBlockIcon3','typeBlockIcon typeBlockIcon4','typeBlockIcon typeBlockIcon5','typeBlockIcon typeBlockIcon6','typeBlockIcon typeBlockIcon7','typeBlockIcon typeBlockIcon8','typeBlockIcon typeBlockIcon9'];
-      if (e.target) {
-        let key = e.target.className?e.target.className:'';
-        if (classes.includes(key)||(typeof key == 'string'&&key.indexOf('typeBlockIcon') != -1)) {
-          return
-        }
-      }
-      setIfOpenPannel(false);
-    });
-    
     // 事件监听器的函数定义
-    const handleStorageChange = (e:any) => {
+    const handleStorageChange = (e: any) => {
       if (e.key === 'tokenExpired') {
         set_tokenUpdateModalVisible(true);
       }
@@ -383,132 +391,167 @@ const TopBar: React.FC<TopBarType> = (props) => {
     // 返回的函数用于在组件卸载时移除事件监听器
     return () => {
       window.removeEventListener('removeLocalItemEvent', handleStorageChange);
+      document.removeEventListener('click', handleClickOutside);
     };
-
   }, []);
 
   return (
-    <div className='topBar' onClick={e => e.stopPropagation()}>
-      <Modal className='TokenUpdateModal' open={tokenUpdateModalVisible} width={400} title={false}  footer={false} closable={false}  >
-           <div className='content'>                                                                                                                                                                         
-                <div className='title'>登录超时锁定</div>
-                <div className='form'>
-                      <div className='avatar'>
-                         <img className='avatarImg' src={require('../../../public/images/initAvatar.png')} alt="" />
-                         <img className='suoding' src={require('../../../public/images/suoding.png')} alt="" />
-                      </div>
-                      <div className='name'>{userData?.name}</div>
-                      <Input onChange={(e)=>set_password(e.target.value)} value={password}  
-                      className='input' autoComplete='off' onKeyDown={(e)=>e.key === 'Enter'&&updateToken()} />
-                      <div className='updateBtn' onClick={()=>updateToken()}>解锁</div>
-                      <a onClick={()=>logoutHandle()}>退出登录</a>
-                </div>
-           </div>
+    <div className="topBar" onClick={(e) => e.stopPropagation()}>
+      <Modal className="TokenUpdateModal" open={tokenUpdateModalVisible} width={400} title={false} footer={false} closable={false}>
+        <div className="content">
+          <div className="title">登录超时锁定</div>
+          <div className="form">
+            <div className="avatar">
+              <img className="avatarImg" src={require('../../../public/images/initAvatar.png')} alt="" />
+              <img className="suoding" src={require('../../../public/images/suoding.png')} alt="" />
+            </div>
+            <div className="name">{userData?.name}</div>
+            <Input onChange={(e) => set_password(e.target.value)} value={password} className="input" autoComplete="off" onKeyDown={(e) => e.key === 'Enter' && updateToken()} />
+            <div className="updateBtn" onClick={() => updateToken()}>
+              解锁
+            </div>
+            <a onClick={() => logoutHandle()}>退出登录</a>
+          </div>
+        </div>
       </Modal>
-      <div className='logoWrap'>
-        {logo&&<img className='logo' src={logo} onClick={() => goToHome()} />}
+      <div className="logoWrap">
+        {logo && <img className="logo" src={logo} onClick={() => goToHome()} />}
         <Divider type="vertical" style={{ background: 'white', height: 16, opacity: 0.29, position: 'relative', top: 1, marginLeft: 16, marginRight: 8 }} />
         <div className={ifOpenPannel ? 'menu active' : 'menu'} onClick={() => openNav()}>
           <img src={require('../../../public/images/menu.png')} alt="" />
         </div>
-        <span className='systemTitle'
-        //  onClick={() => goSystemIndex(pageTitle)}
-         >{pageTitle}</span>
+        <span
+          className="systemTitle"
+          //  onClick={() => goSystemIndex(pageTitle)}
+        >
+          {initialState?.pageTitle}
+        </span>
       </div>
 
-      <div className='userRelaInfoWrap'>
+      <div className="userRelaInfoWrap">
         <>
           {/**
            * 已打开的tab
            */}
-          <div className='tabWrap'>
-            {onTabSystemTabs && (onTabSystemTabs).map((item, index) => (
-              <div key={index} className={currentSelectedTab?.menuId == item.menuId ? `tab on` : `tab`} onClick={() => _systemTabClickHandle(item)}>
-                <div className='tabText'>{item.name} </div>
-                <div className='closeIconWrap'>
-                  <img src={tabCloseIcon} onClick={(e) => closeTabHandle(item, e)} />
+          <div className="tabWrap">
+            {onTabSystemTabs &&
+              onTabSystemTabs.map((item, index) => (
+                <div key={index} className={currentSelectedTab?.menuId == item.menuId ? `tab on` : `tab`} onClick={() => _systemTabClickHandle(item)}>
+                  <div className="tabText">{item.name} </div>
+                  <div className="closeIconWrap">
+                    <img src={tabCloseIcon} onClick={(e) => closeTabHandle(item, e)} />
+                  </div>
                 </div>
-              </div>
-            ))}
+              ))}
           </div>
-          {systemTabs.length > 5 && <div className={showMoreTabPannel ? 'tabMore active' : 'tabMore'} onMouseEnter={() => set_showMoreTabPannel(true)} onMouseLeave={handleTabMoreMouseLeave} >
-            {showMoreTabPannel && (<div className='morePannel'
-              onMouseEnter={handleMorePannelMouseEnter}
-              onMouseLeave={handleMorePannelMouseLeave}
-            >
-              {
-                onTabSystemTabs_hide.map((item, index) => {
-                  return (<div key={index} className='moreItem' onClick={() => moreItemClickHandle(item)}>
-                    {/* <Tooltip placement="right" title={item.name}> */}
-                    <span>{item.name}</span>
-                    {/* </Tooltip> */}
-                  </div>)
-                })
-              }
-            </div>)}
-          </div>}
+          {systemTabs.length > 5 && (
+            <div className={showMoreTabPannel ? 'tabMore active' : 'tabMore'} onMouseEnter={() => set_showMoreTabPannel(true)} onMouseLeave={handleTabMoreMouseLeave}>
+              {showMoreTabPannel && (
+                <div className="morePannel" onMouseEnter={handleMorePannelMouseEnter} onMouseLeave={handleMorePannelMouseLeave}>
+                  {onTabSystemTabs_hide.map((item, index) => {
+                    return (
+                      <div key={index} className="moreItem" onClick={() => moreItemClickHandle(item)}>
+                        {/* <Tooltip placement="right" title={item.name}> */}
+                        <span>{item.name}</span>
+                        {/* </Tooltip> */}
+                      </div>
+                    );
+                  })}
+                </div>
+              )}
+            </div>
+          )}
         </>
 
-
-        <div className='notification'>
-          <img className='notificationIcon' src={require('../../../public/images/notificationIcon.png')} />
+        <div className="notification">
+          <img className="notificationIcon" src={require('../../../public/images/notificationIcon.png')} />
         </div>
-        <Tooltip className='topBarTooltip' placement='bottomRight' title={<UserPannel />} color="#fff" onOpenChange={(visible) => setArrowRotate(visible)}>
-          <div className='user'>
-            <div className='avator'><img src={userData?.avatarUrl?userData.avatarUrl:require('../../../public/images/avatar.png')} /></div>
-            <div className='info'>
-              <span className='hospName'>{localStorage.getItem('hospAbbreviation')}</span>
-              <span className='name'>{userData?.name}</span>
+        <div className="group">
+          {groupList.length > 0 && (
+            <Tooltip title={currentActivedGroup?.orgName ?? ''} placement="right">
+              <div
+                className="group-switcher"
+                onClick={() => {
+                  isClickInside = true;
+                  set_showGroupList(true);
+                }}
+              >
+                <img src={require('./images/groupIcon.png')} alt="" />
+              </div>
+            </Tooltip>
+          )}
+          {showGroupList && (
+            <div className="group-list-wrapper" ref={GroupListWrapperRef}>
+              {groupList.map((a, index) => (
+                <div key={index} className={currentActivedGroup.orgId == a.orgId ? `group-list actived` : 'group-list'} onClick={() => switchGroupHandle(a)}>
+                  {a.orgName}
+                </div>
+              ))}
+            </div>
+          )}
+        </div>
+        <Tooltip className="topBarTooltip" placement="bottomRight" title={<UserPannel />} color="#fff" onOpenChange={(visible) => setArrowRotate(visible)}>
+          <div className="user">
+            <div className="avator">
+              <img src={userData?.avatarUrl ? userData.avatarUrl : require('../../../public/images/avatar.png')} />
+            </div>
+            <div className="info">
+              <span className="hospName">{localStorage.getItem('hospAbbreviation')}</span>
+              <span className="name">{userData?.name}</span>
             </div>
             {/* <img className={arrowRotate ? `arrow on` : `arrow`} src={require('../../../public/images/arrow_white.png')} /> */}
           </div>
         </Tooltip>
       </div>
 
-      {
-        ifOpenPannel && (
-          <div className='panel' onClick={e => e.stopPropagation()}>
-            <div className='left'>
-              {
-                panelData.map((item, index) => {
-                  return (
-                    <div className={currentActivedBlockIndex == index ? `typeBlock active` : `typeBlock`} key={index} onClick={() => set_currentActivedBlockIndex(index)}>
-                      {/* <img className='typeBlockIcon' src={item?.icon} alt="" /> */}
-                      <div className={` typeBlockIcon typeBlockIcon${item.icon}`} ></div>
-                      <span className='typeBlockName'>{item.name}</span>
-                    </div>
-                  )
-                })
-              }
-            </div>
-            <div className='right'>
-              <div className='panelCloseBtn' onClick={() => setIfOpenPannel(false)} ></div>
-              {
-                panelData.length > 0 && panelData[currentActivedBlockIndex] && panelData[currentActivedBlockIndex].child && panelData[currentActivedBlockIndex].child.map((item, index: number) => {
-                  return (
-                    <div className='row' key={index}>
-                      <img className='channelIcon' src={item.icon?item.icon:require(`../../../public/images/tongyong_tixi.png`)} alt="" />
-                      <div className='rowDetail'>
-                        <div className='channelName' onClick={() => goChannelIndex(item)}>{item.name}</div>
-                        <div className='channelList'>
-                          {
-                            item.child && item.child.length > 0 && item.child.map((val, i: number) => {
-                              return (
-                                <div className={currentSelectedTab?.menuId == val.menuId ? 'systemTab on' : 'systemTab'} key={i} onClick={() => _systemListClickHandle(val, currentActivedBlockIndex, index, i)}>{val.name}</div>
-                              )
-                            })
-                          }
-                        </div>
+      {ifOpenPannel && (
+        <div className="panel" ref={PannelRef} onClick={(e) => e.stopPropagation()}>
+          <div className="left">
+            {panelData.map((item, index) => {
+              return (
+                <div className={currentActivedBlockIndex == index ? `typeBlock active` : `typeBlock`} key={index} onClick={() => set_currentActivedBlockIndex(index)}>
+                  {/* <img className='typeBlockIcon' src={item?.icon} alt="" /> */}
+                  <div className={` typeBlockIcon typeBlockIcon${item.icon}`}></div>
+                  <span className="typeBlockName">{item.name}</span>
+                </div>
+              );
+            })}
+          </div>
+          <div className="right">
+            <div className="panelCloseBtn" onClick={() => setIfOpenPannel(false)}></div>
+            {panelData.length > 0 &&
+              panelData[currentActivedBlockIndex] &&
+              panelData[currentActivedBlockIndex].child &&
+              panelData[currentActivedBlockIndex].child.map((item, index: number) => {
+                return (
+                  <div className="row" key={index}>
+                    <img className="channelIcon" src={item.icon ? item.icon : require(`../../../public/images/tongyong_tixi.png`)} alt="" />
+                    <div className="rowDetail">
+                      <div className="channelName" onClick={() => goChannelIndex(item)}>
+                        {item.name}
+                      </div>
+                      <div className="channelList">
+                        {item.child &&
+                          item.child.length > 0 &&
+                          item.child.map((val, i: number) => {
+                            return (
+                              <div
+                                className={currentSelectedTab?.menuId == val.menuId ? 'systemTab on' : 'systemTab'}
+                                key={i}
+                                onClick={() => _systemListClickHandle(val, currentActivedBlockIndex, index, i)}
+                              >
+                                {val.name}
+                              </div>
+                            );
+                          })}
                       </div>
                     </div>
-                  )
-                })
-              }
-            </div>
+                  </div>
+                );
+              })}
           </div>
-        )
-      }
-
+        </div>
+      )}
     </div>
   );
 };

+ 84 - 2
src/components/topBar/style.less

@@ -8,6 +8,7 @@
 .TokenUpdateModal {
   .kcmp-ant-modal-content {
     background: linear-gradient(180deg, #e8f1fc 0%, #ffffff 100%) !important;
+
     .kcmp-ant-modal-body {
       background-image: url('../../../public/images/tokenUpdateModalbg.png') !important;
       background-size: 320px 180px !important;
@@ -35,10 +36,12 @@
             width: 64px;
             height: 64px;
             margin-bottom: 8px;
+
             .avatarImg {
               width: 100%;
               height: 100%;
             }
+
             .suoding {
               position: absolute;
               bottom: 0;
@@ -363,6 +366,61 @@
       }
     }
 
+    .group {
+      position: relative;
+
+      .group-switcher {
+        display: flex;
+        cursor: pointer;
+        justify-content: center;
+        align-items: center;
+        width: 32px;
+        height: 32px;
+        border-radius: 4px;
+        margin-right: 8px;
+
+        & > img {
+          width: 24px;
+          height: 24px;
+        }
+
+        &:hover {
+          background: rgba(249, 248, 248, 0.1);
+        }
+      }
+
+      .group-list-wrapper {
+        position: absolute;
+        padding: 4px;
+        top: 40px;
+        background: #ffffff;
+        border-radius: 4px;
+        box-shadow: 0px 8px 16px 0px rgba(64, 85, 128, 0.1);
+        .group-list {
+          width: 152px;
+          height: 32px;
+          text-align: left;
+          border-radius: 2px;
+          font-weight: 400;
+          font-size: 14px;
+          color: #17181a;
+          line-height: 32px;
+          padding-left: 8px;
+          cursor: pointer;
+          &.actived {
+            font-weight: bold;
+            color: #17181a;
+            background: rgba(51, 119, 255, 0.08);
+          }
+          &:hover {
+            font-weight: bold;
+            color: #17181a;
+            background: rgba(51, 119, 255, 0.08);
+          }
+        }
+      }
+    }
+
     .user {
       cursor: pointer;
       display: flex;
@@ -389,8 +447,8 @@
         border: 1px solid #ffffff;
 
         img {
-          width: 101%;
-          height: 101%;
+          width: 32px;
+          height: 32px;
         }
       }
 
@@ -504,6 +562,14 @@
           background: url('../../../public/images/feiyi_gray.png');
           background-size: contain;
         }
+        .typeBlockIcon6 {
+          background: url('../../../public/images/pcbankuaiguanli_gray.png');
+          background-size: contain;
+        }
+        .typeBlockIcon7 {
+          background: url('../../../public/images/appbankuaiguanli_gray.png');
+          background-size: contain;
+        }
 
         &.active {
           background: #f2f7ff;
@@ -532,6 +598,14 @@
             background: url('../../../public/images/feiyi.png');
             background-size: contain;
           }
+          .typeBlockIcon6 {
+            background: url('../../../public/images/pcbankuaiguanli.png');
+            background-size: contain;
+          }
+          .typeBlockIcon7 {
+            background: url('../../../public/images/appbankuaiguanli.png');
+            background-size: contain;
+          }
 
           & > span {
             color: #3376fe;
@@ -564,6 +638,14 @@
             background: url('../../../public/images/feiyi.png');
             background-size: contain;
           }
+          .typeBlockIcon6 {
+            background: url('../../../public/images/pcbankuaiguanli.png');
+            background-size: contain;
+          }
+          .typeBlockIcon7 {
+            background: url('../../../public/images/appbankuaiguanli.png');
+            background-size: contain;
+          }
 
           & > span {
             color: #3376fe;

+ 4 - 8
src/global.less

@@ -15,7 +15,6 @@ input {
   outline: none !important;
 }
 
-
 .ant-notification {
   display: none !important;
 }
@@ -55,7 +54,7 @@ input {
   border-radius: 4px;
   border: 1px solid #cfd7e6;
 
-  &>input {
+  & > input {
     &::placeholder {
       font-size: 14px;
       font-family: SourceHanSansCN-Normal, SourceHanSansCN;
@@ -89,7 +88,6 @@ input {
     .kcmp-ant-form-item-control-input-content {
       textarea {
         min-height: 24px !important;
-        height: 24px !important;
         padding: 0 8px !important;
         border-radius: 4px !important;
         // border: 1px solid #cfd7e6;
@@ -100,7 +98,6 @@ input {
       }
 
       .kcmp-ant-input {
-
         //padding-left: 8px;
         &::placeholder {
           color: #99a6bf;
@@ -147,7 +144,7 @@ input {
   }
 }
 
-.kcmp-ant-table-thead>tr>th {
+.kcmp-ant-table-thead > tr > th {
   border-bottom: none;
 
   &::before {
@@ -171,7 +168,6 @@ input {
     // }
 
     .kcmp-ant-select-selection-item {
-
       // height: 16px !important;
       // line-height: 16px !important;
       margin-top: 0 !important;
@@ -392,7 +388,7 @@ input {
       .kcmp-ant-table-container {
         .kcmp-ant-table-content {
           .kcmp-ant-table-thead {
-            &>tr>th {
+            & > tr > th {
               font-size: 14px;
               font-family: SourceHanSansCN-Medium, SourceHanSansCN;
               font-weight: 500;
@@ -416,7 +412,7 @@ input {
   }
 }
 
-.kcmp-ant-checkbox+span {
+.kcmp-ant-checkbox + span {
   padding-left: 4px;
   padding-right: 4px;
 }

+ 61 - 32
src/layouts/index.tsx

@@ -1,7 +1,7 @@
 /*
  * @Author: your name
  * @Date: 2021-11-09 13:56:33
- * @LastEditTime: 2024-11-19 16:39:20
+ * @LastEditTime: 2025-03-13 10:35:15
  * @LastEditors: code4eat awesomedema@gmail.com
  * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  * @FilePath: /KC-MiddlePlatform/src/layouts/index.tsx
@@ -11,11 +11,16 @@ import { IRouteComponentProps, useModel, history } from 'umi';
 import ProLayout from '@ant-design/pro-layout';
 
 import TopBar from '@/components/topBar';
-import { Key, useEffect, useState } from 'react';
+import { Key, useEffect, useRef, useState } from 'react';
 import { getSpecifyMenuDetail, getUserPlatformNav } from '@/service/menu';
 import { getAppAccess, getSysParamsByCode } from '@/service';
-import { Modal } from 'antd';
+import { Input, Modal } from 'antd';
+import './style.less';
 import { KcimCenterSysId } from '@/constant';
+import { askAiReq } from '@/service/ai';
+import { createFromIconfontCN } from '@ant-design/icons';
+import ReactMarkdown from 'react-markdown';
+import AiChat from '@/components/intelligenceBot';
 
 const TopHoc = ({
   currentSelectedSys,
@@ -38,16 +43,15 @@ const TopHoc = ({
 
   const onTabClickHandle = async (data: TopBar.Tab) => {
     const { systemId = '', menuId = '' } = data;
-    
+
     const resp = await getAppAccess(systemId.length > 0 ? systemId : menuId);
 
     if (!resp) {
       if (JSON.stringify(data) != '{}') {
-
-        if(data.contentType != 7){
-          await setInitialState((s) => ({ ...s, currentSelectedSys: data }) as any);
+        if (data.contentType != 7) {
+          await setInitialState((s) => ({ ...s, currentSelectedSys: data } as any));
         }
-        
+
         localStorage.removeItem('selectedKeys');
 
         localStorage.removeItem('openKeys');
@@ -66,24 +70,21 @@ const TopHoc = ({
           } else {
             localStorage.setItem('isChildShowMenu', 'true');
           }
-          if (data.contentType == 7 &&data.type == 1) {
+          if (data.contentType == 7 && data.type == 1) {
             window.open(data.url, '_blank');
-          }else{
+          } else {
             history.push(data.path);
           }
-          
         }
       }
     } else {
       Modal.error({
         title: '当前系统未注册,请联系管理员处理!',
       });
-      
     }
   };
 
   const userPannelTabClickhandle = (tag: string) => {
-
     if (tag == 'LOGOUT') {
       if (initialState) {
         const { logout } = initialState;
@@ -116,7 +117,7 @@ const TopHoc = ({
       navData={navData}
       logo={logo}
       topBarTitle={topBarTitle}
-      userData={JSON.parse((localStorage.getItem('userInfo')?localStorage.getItem('userInfo'):localStorage.getItem('userData')) as string)}
+      userData={JSON.parse((localStorage.getItem('userInfo') ? localStorage.getItem('userInfo') : localStorage.getItem('userData')) as string)}
       openedTabs={openedSysLists}
       onTabChange={onTabChangeHandle}
       onTabClick={onTabClickHandle}
@@ -126,6 +127,18 @@ const TopHoc = ({
   );
 };
 
+/**
+ * 定义单条消息的类型
+ */
+type Message = {
+  sender: 'user' | 'bot';
+  content: string; // 可能包含纯文本或HTML
+};
+
+const IconFont = createFromIconfontCN({
+  scriptUrl: '',
+});
+
 export default function Layout({ children, location, route, history, match }: IRouteComponentProps) {
   const { initialState, setInitialState } = useModel('@@initialState');
   const [navData, set_navData] = useState<TopBar.PanelData[]>([]);
@@ -133,15 +146,20 @@ export default function Layout({ children, location, route, history, match }: IR
   const [isEmpty, set_isEmpty] = useState(false);
   const [topBarLogoUrl, set_topBarLogoUrl] = useState<string>('');
   const [topBarTitle, set_topBarTitle] = useState('');
-   
+
   const queryParams = new URLSearchParams(location.search);
   const noTopBar = queryParams.get('noTopbar');
-  
+  const [ifStartChat, set_ifStartChat] = useState(false);
+  const [messages, setMessages] = useState<Message[]>([]);
+  const [inputValue, setInputValue] = useState<string>('');
+  const [loading, setLoading] = useState<boolean>(false);
+  const chatContainerRef = useRef<HTMLDivElement>(null);
+
   const getNavData = async () => {
     const nav = await getUserPlatformNav();
     if (nav) {
       set_navData(nav as any);
-      setInitialState((t)=>({...t,navData:nav}) as any)
+      setInitialState((t) => ({ ...t, navData: nav } as any));
     }
   };
 
@@ -150,10 +168,10 @@ export default function Layout({ children, location, route, history, match }: IR
     if (resp) {
       resp.forEach((item: any) => {
         if (item.code == '1739122834787143680') {
-          set_topBarLogoUrl(item.value);
+          item.value && set_topBarLogoUrl(item.value);
         }
         if (item.code == '1739123252502073344') {
-          set_topBarTitle(item.value);
+          item.value && set_topBarTitle(item.value);
         }
       });
     }
@@ -235,18 +253,23 @@ export default function Layout({ children, location, route, history, match }: IR
         },
       }}
       pageTitleRender={false}
-      headerRender={noTopBar != 'true'?() =>{
-        return (initialState ) && (
-          <TopHoc
-            navData={navData}
-            topBarTitle={topBarTitle}
-            logo={topBarLogoUrl as string}
-            set_emptyPageContent={set_emptyPageContent}
-            currentSelectedSys={initialState.currentSelectedSys}
-            openedSysLists={initialState.openedSysLists ? initialState.openedSysLists : []}
-          />
-        )
-      }:false
+      headerRender={
+        noTopBar != 'true'
+          ? () => {
+              return (
+                initialState && (
+                  <TopHoc
+                    navData={navData}
+                    topBarTitle={topBarTitle}
+                    logo={topBarLogoUrl as string}
+                    set_emptyPageContent={set_emptyPageContent}
+                    currentSelectedSys={initialState.currentSelectedSys}
+                    openedSysLists={initialState.openedSysLists ? initialState.openedSysLists : []}
+                  />
+                )
+              );
+            }
+          : false
       }
     >
       {isEmpty && (
@@ -254,7 +277,13 @@ export default function Layout({ children, location, route, history, match }: IR
           <h1>{emptyPageContent}</h1>
         </div>
       )}
-      {!isEmpty && <div style={{ height:noTopBar == 'true'?'auto': `calc(100vh - 48px)`, overflowY: 'scroll', minWidth: 1280 }}>{children}</div>}
+      {!isEmpty && (
+        <div className="platform-children" style={{ height: noTopBar == 'true' ? 'auto' : `calc(100vh - 48px)`, minWidth: 1280 }}>
+          <AiChat />
+
+          <>{children}</>
+        </div>
+      )}
     </ProLayout>
   );
 }

+ 143 - 0
src/layouts/style.less

@@ -0,0 +1,143 @@
+.platform-children {
+  .aiBtn {
+    position: fixed;
+    cursor: pointer;
+    right: 12px;
+    z-index: 10;
+    bottom: 12px;
+    width: 50px;
+    height: 50px;
+    box-shadow: 0px 10px 20px 0px rgba(77, 102, 153, 0.3);
+    border-radius: 12px;
+
+    & > img {
+      width: 100%;
+      height: 100%;
+    }
+  }
+  .chat {
+    position: fixed;
+    z-index: 99;
+    right: 20px;
+    bottom: 40px;
+    width: 560px;
+    height: 640px;
+    padding: 8px;
+    background: rgba(240, 246, 252, 0.98);
+    box-shadow: 0px 8px 16px 0px rgba(64, 85, 128, 0.2);
+    border-radius: 16px;
+    border: 1px solid #ffffff;
+    .chat-header {
+      display: flex;
+      flex: 1;
+      padding: 0 8px;
+      flex-direction: row;
+      justify-content: flex-start;
+      align-items: center;
+      margin-bottom: 9px;
+
+      .chat-header-title {
+        display: flex;
+        flex: 1;
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: center;
+        & > img {
+          width: 40px;
+          height: 30px;
+        }
+        & > span {
+          font-weight: 500;
+          font-size: 16px;
+          color: #17181a;
+          padding-left: 8px;
+        }
+      }
+      .closeBtn {
+        cursor: pointer;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        width: 24px;
+        height: 24px;
+        border-radius: 4px;
+        &:hover {
+          background: #f5f5f5;
+        }
+      }
+    }
+    .chat-content {
+      display: flex;
+      flex: 1;
+      width: 100%;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+      height: calc(100% - 40px);
+      background: #ffffff;
+      border-radius: 8px;
+      & > img {
+        width: 480px;
+        height: 200px;
+      }
+      .chat-container {
+        display: flex;
+        width: 100%;
+        flex-direction: column;
+        gap: 10px; // 每条消息之间的间距
+        background-color: #fff;
+        padding: 10px;
+        border-radius: 5px;
+
+        height: 440px; // 固定高度,便于滚动展示
+        overflow-y: auto;
+        .message {
+          padding: 10px;
+          border-radius: 5px;
+          max-width: 70%;
+          word-wrap: break-word; // 自动换行
+        }
+        .user-message {
+          align-self: flex-end;
+          background-color: #e3f2fd;
+        }
+        .bot-message {
+          align-self: flex-start;
+          padding-left: 35px;
+          background-color: #f5f5f5;
+        }
+      }
+
+      .startInput {
+        display: flex;
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: center;
+        width: 480px;
+        height: 56px;
+        padding: 12px;
+        margin-top: 20px;
+        background: #fcfeff;
+        border-radius: 16px;
+        border: 1px solid #dae6f2;
+
+        .chat-sendBtn {
+          display: flex;
+          cursor: pointer;
+          justify-content: center;
+          align-items: center;
+          width: 32px;
+          height: 32px;
+          margin-left: 22px;
+          background: #3377ff;
+          border-radius: 8px;
+
+          img {
+            width: 24px;
+            height: 24px;
+          }
+        }
+      }
+    }
+  }
+}

+ 1 - 2
src/pages/document.ejs

@@ -1,7 +1,7 @@
 <!--
  * @Author: your name
  * @Date: 2022-02-14 14:02:45
- * @LastEditTime: 2024-04-29 11:08:58
+ * @LastEditTime: 2024-12-10 16:47:13
  * @LastEditors: code4eat awesomedema@gmail.com
  * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  * @FilePath: /KC-MiddlePlatform/src/pages/document.ejs
@@ -17,7 +17,6 @@
   name="viewport"
   content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
 />
-  <title>精益管理中台</title>
   <link rel="icon" href="/favicon.ico" type="image/x-icon" id="favicon">
   <!-- <link rel="icon" href="<%= context.favicon %>" type="image/x-icon"> -->
   <!-- <link rel="icon" href="<%= context.config.publicPath +'favicon.ico'%>" type="image/x-icon" /> -->

+ 20 - 94
src/pages/index/components/FastEntry/index.tsx

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2022-05-27 14:17:59
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-10-12 15:08:21
+ * @LastEditTime: 2025-01-15 10:45:43
  * @FilePath: /KC-MiddlePlatform/src/pages/index/components/FastEntry/index.tsx
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -14,19 +14,22 @@ import './style.less';
 import { Empty, Modal } from 'antd';
 import NavSelecter, { NavSelecterData, NavSelecterItemType } from '@/components/NavSelecter';
 import { addFastEntry, AddFastEntryDataType, getUserPlatformNav } from '@/service/menu';
-import { getAppAccess } from '@/service';
+import { getAppAccess, removeFastEntrance } from '@/service';
 import { createFromIconfontCN } from '@ant-design/icons';
 
 export type FastEntryTabType = {
+  id: string;
   name: string;
   path: string;
   systemId: string;
   type: number;
   contentType: number;
   menuId: number | string;
+  url?: string;
 };
 export interface FastEntryType {
   data: FastEntryTabType[];
+  onChange?: () => void;
 }
 
 const IconFont = createFromIconfontCN({
@@ -35,27 +38,25 @@ const IconFont = createFromIconfontCN({
 
 export const FastEntry = (props: FastEntryType) => {
   const { initialState, setInitialState } = useModel('@@initialState');
-
   const [tabs, set_tabs] = useState<FastEntryTabType[]>([]);
   const [open, set_open] = useState(false);
   const [navSelecterData, set_navSelecterData] = useState<NavSelecterData[]>([]);
 
   const tabClickHandle = async (tab: FastEntryTabType) => {
-    const { systemId = '', menuId = '', contentType, type,url } = tab;
+    const { systemId = '', menuId = '', contentType, type, url } = tab;
     const resp = await getAppAccess(systemId.length > 0 ? systemId : menuId);
     if (!resp) {
-      if(contentType != 7 && type == 1){
-        await setInitialState((s) => ({ ...s, currentSelectedSys: tab as any, currentTab: tab }));
-      }
-     
-      if (type == 1 && contentType == 6) {
-        //体系或第三方iframe
-        history.push('/platform');
-      }else if(type == 1 && contentType == 7){
-        window.open(url,'_blank');
-      }else {
-        history.push(tab.path);
+      if (contentType != 7 && type != 1) {
+        setInitialState((s: any) => ({ ...s, currentSelectedSys: tab as any, currentTab: tab }));
       }
+      // if (type == 1 && contentType == 6) {
+      //   //体系或第三方iframe
+      //   history.push('/platform');
+      // }else if(type == 1 && contentType == 7){
+      //   window.open(url,'_blank');
+      // }else {
+      //   history.push(tab.path);
+      // }
     } else {
       Modal.error({
         title: '当前系统未注册,请联系管理员处理!',
@@ -69,9 +70,11 @@ export const FastEntry = (props: FastEntryType) => {
   };
 
   const onCheckedHandle = async (data: NavSelecterItemType[]) => {
+    const { onChange } = props;
     const resp = await addFastEntry(data as AddFastEntryDataType[]);
     if (resp) {
       set_tabs(data);
+      onChange && onChange();
     }
   };
 
@@ -85,84 +88,7 @@ export const FastEntry = (props: FastEntryType) => {
     set_tabs(data);
   }, [props]);
 
-  useEffect(() => {
-    // set_navSelecterData([
-    //       {
-    //         name:'精益医疗管理',
-    //         key:0,
-    //         children:[
-    //             {
-    //                 name:'质量安全管理',
-    //                 key:1,
-    //                 children:[
-    //                     {
-    //                         name:'质量管理及十大安全目标',
-    //                         key:2
-    //                     },
-    //                     {
-    //                         name:'质量管理及十大安全目标',
-    //                         key:3
-    //                     },
-    //                     {
-    //                         name:'质量管理及十大安全目标',
-    //                         key:4
-    //                     },
-    //                     {
-    //                         name:'质量管理及十大安全目标',
-    //                         key:20
-    //                     },
-    //                     {
-    //                         name:'质量管理及十大安全目标',
-    //                         key:21
-    //                     }
-    //                 ]
-    //             },
-    //             {
-    //                 name:'质量安全管理2',
-    //                 key:30,
-    //                 children:[
-    //                     {
-    //                         name:'质量管理及十大安全目标',
-    //                         key:31
-    //                     },
-    //                     {
-    //                         name:'质量管理及十大安全目标',
-    //                         key:32
-    //                     },
-    //                     {
-    //                         name:'质量管理及十大安全目标',
-    //                         key:33
-    //                     },
-    //                     {
-    //                         name:'质量管理及十大安全目标',
-    //                         key:34
-    //                     },
-    //                     {
-    //                         name:'质量管理及十大安全目标',
-    //                         key:35
-    //                     }
-    //                 ]
-    //             }
-    //         ]
-    //       },
-    //       {
-    //         name:'HBI',
-    //         key:5,
-    //         children:[
-    //             {
-    //                 name:'质量安全管理',
-    //                 key:6,
-    //                 children:[
-    //                     {
-    //                         name:'质量管理及十大安全目标',
-    //                         key:7
-    //                     }
-    //                 ]
-    //             }
-    //         ]
-    //       }
-    // ])
-  }, []);
+  useEffect(() => {}, []);
 
   return (
     <div className="fastEntry">
@@ -173,7 +99,7 @@ export const FastEntry = (props: FastEntryType) => {
         </span>
       </div>
 
-      {open && <NavSelecter type={2} title="选择应用" data={navSelecterData} onVisibleChange={(bool) => set_open(bool)} value={tabs.map((a) => a.menuId)} onChecked={onCheckedHandle} />}
+      {open && <NavSelecter type={2} resetHandleType="request" title="选择应用" data={navSelecterData} onVisibleChange={(bool) => set_open(bool)} value={tabs} onChecked={onCheckedHandle} />}
 
       <div className="wrap">
         {tabs.slice(0, 6).map((item, index) => {

+ 52 - 33
src/pages/index/index.tsx

@@ -1,7 +1,7 @@
 /*
  * @Author: your name
  * @Date: 2021-11-10 09:33:30
- * @LastEditTime: 2024-07-25 15:03:07
+ * @LastEditTime: 2025-03-13 14:12:10
  * @LastEditors: code4eat awesomedema@gmail.com
  * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  * @FilePath: /KC-MiddlePlatform/src/pages/index.tsx
@@ -20,10 +20,9 @@ import { getSysParamsByCode, getUserIndexData, UserInfo } from '@/service';
 import { KcimCenterSysId } from '@/constant';
 import { createFromIconfontCN } from '@ant-design/icons';
 
-
-import '../../../public/zhongtaiB.js'
+import '../../../public/zhongtaiB.js';
 const IconFont = createFromIconfontCN({
-  scriptUrl:'',
+  scriptUrl: '',
 });
 
 export interface IndexPageType {
@@ -35,11 +34,13 @@ const IndexPage: React.FC<IndexPageType> = ({ location }) => {
     systemLists, //当前医院可选子系统列表
     setInitialState,
     userData,
+    customerType,
   } = useModel('@@initialState', (model) => {
     return {
       systemLists: model.initialState?.systemLists,
       setInitialState: model.setInitialState,
       userData: model.initialState?.userData,
+      customerType: model.initialState?.customerType,
     };
   });
 
@@ -73,8 +74,9 @@ const IndexPage: React.FC<IndexPageType> = ({ location }) => {
       set_leftImgUrl(resp.indexUrl ? resp.indexUrl.split('|')[0] : undefined);
       set_leftBgImgUrl(resp.indexUrl ? resp.indexUrl.split('|')[1] : undefined);
       localStorage.setItem('userInfo', JSON.stringify(resp.userInfo));
-
+      setInitialState((s: any) => ({ ...s, userInfo: resp.userInfo }));
       const fastEntryList = resp.fastEntrance.map((item: any) => ({
+        id: item.id,
         name: item.name,
         path: item.path,
         menuId: item.menuId,
@@ -139,11 +141,14 @@ const IndexPage: React.FC<IndexPageType> = ({ location }) => {
     }
   };
 
-
   const onClose = () => {
     set_drawerOpen(false);
   };
 
+  const fastEntryOnChangeHandle = () => {
+    getIndexPageDataFunc();
+  };
+
   useEffect(() => {
     if (allParams) {
       setIframeUrl(allParams);
@@ -165,7 +170,7 @@ const IndexPage: React.FC<IndexPageType> = ({ location }) => {
       const visitedPaths = JSON.parse(t);
       set_recentlyVisitedList(visitedPaths);
     }
-    
+
     getAllParamsHanle();
 
     // const url = `${spacialPage[0].url}&token=${youshuToken}`;
@@ -173,32 +178,46 @@ const IndexPage: React.FC<IndexPageType> = ({ location }) => {
 
   return (
     <div className="indexPage">
-      <Drawer className='MsgProcessDrawer'  title={null}  onClose={onClose} open={drawerOpen} mask={false} headerStyle={{display:'none'}}
-         bodyStyle={{background:'#F5F7FA'}}
-         width={600}
-         style={{ 
+      {/* <iframe width={1000} height={800} src="http://localhost:8088/superset/dashboard/5b12b583-8204-08e9-392c-422209c29787/?native_filters_key=q3H6TKjSa58" ></iframe> */}
+      <Drawer
+        className="MsgProcessDrawer"
+        title={null}
+        onClose={onClose}
+        open={drawerOpen}
+        mask={false}
+        headerStyle={{ display: 'none' }}
+        bodyStyle={{ background: '#F5F7FA' }}
+        width={600}
+        style={{
           position: 'absolute', // 确保 Drawer 可以被定位
-          top:48,
-          bottom:0, // 距离底部 10%
-          height:'calc(100vh - 50px)', // 设置 Drawer 的高度
+          top: 48,
+          bottom: 0, // 距离底部 10%
+          height: 'calc(100vh - 50px)', // 设置 Drawer 的高度
         }}
       >
-          <div className='drawerHeader'>
-                 <div className='title'><span onClick={()=>set_drawerOpen(false)}><img src={require('../../../public/images/cancel_black.png')} alt="" /></span>待办事项</div>
+        <div className="drawerHeader">
+          <div className="title">
+            <span onClick={() => set_drawerOpen(false)}>
+              <img src={require('../../../public/images/cancel_black.png')} alt="" />
+            </span>
+            待办事项
           </div>
-          <div className='msgInfo'>
-               <div className='left'>
-                    <img src={require('../../../public/images/todojindo.png')} alt="" />
-                    <div className='detail'>
-                         <div className='msgtitle'>任立群发起了请假申请</div>
-                         <div className='date'>2023-12-26 11:24</div>
-                    </div>
-               </div>
-               <div className='right'>后勤管理 / 报事报修</div>
+        </div>
+        <div className="msgInfo">
+          <div className="left">
+            <img src={require('../../../public/images/todojindo.png')} alt="" />
+            <div className="detail">
+              <div className="msgtitle">任立群发起了请假申请</div>
+              <div className="date">2023-12-26 11:24</div>
+            </div>
           </div>
-          <div className='content'>
-            <div style={{padding:8}}><Skeleton active paragraph={{ rows: 12 }} /></div>
+          <div className="right">后勤管理 / 报事报修</div>
+        </div>
+        <div className="content">
+          <div style={{ padding: 8 }}>
+            <Skeleton active paragraph={{ rows: 12 }} />
           </div>
+        </div>
       </Drawer>
       <Helmet>
         <title>{welcomTitle}</title>
@@ -222,12 +241,11 @@ const IndexPage: React.FC<IndexPageType> = ({ location }) => {
                 <img src={leftImgUrl} alt="" />
               </div>
             )}
-            
           </div>
         </div>
         <div className="right">
           <div className="cardOne">
-            <img className="welcomBg" src={require('../../../public/images/welcom_bg.png')} alt="" />
+            <img className="welcomBg" src={'/images/welcom_bg.png'} alt="" />
             <div className="inner">
               <span className="userId">{userInfo?.account}</span>
               <div className="cardOneTitle">{`欢迎回来,${userInfo?.name}`}</div>
@@ -235,13 +253,14 @@ const IndexPage: React.FC<IndexPageType> = ({ location }) => {
                 <div className="row">
                   <div className="label">
                     <img src={require('../../../public/images/icon-keshi.png')} alt="" />
-                    <span>科室</span>
+                    <span>{customerType == '2' ? '组织' : '科室'}</span>
                     {userInfo?.departmentName ? userInfo?.departmentName : '-'}
                   </div>
                   <div className="label">
                     <img src={require('../../../public/images/icon-zhicheng.png')} alt="" />
-                    <span>职称</span>
-                    {userInfo?.title ? userInfo?.title : '-'}
+                    <span>{customerType == '2' ? '职务' : '职称'}</span>
+                    {customerType == '2' && <>{userInfo?.jobTitle ?? '-'}</>}
+                    {customerType == '1' && <>{userInfo?.title ?? '-'}</>}
                   </div>
                 </div>
                 <div className="row">
@@ -260,7 +279,7 @@ const IndexPage: React.FC<IndexPageType> = ({ location }) => {
             </div>
           </div>
           <div className="cardTwo">
-            <FastEntry data={fastEntry} />
+            <FastEntry data={fastEntry} onChange={() => fastEntryOnChangeHandle()} />
           </div>
           <div className="cardThree">
             <TodoList todoList={todoList} todoListClickHandle={todoListClickHandle} />

+ 40 - 0
src/pages/login/ErrorBoundary.tsx

@@ -0,0 +1,40 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2024-12-11 11:07:41
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2024-12-11 11:08:26
+ * @FilePath: /KC-MiddlePlatform/src/pages/login/ErrorBoundary.tsx
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+import React from 'react';
+
+interface ErrorBoundaryState {
+  hasError: boolean;
+  error: Error | null;
+  errorInfo: React.ErrorInfo | null;
+}
+
+export class ErrorBoundary extends React.Component<{}, ErrorBoundaryState> {
+  constructor(props: {}) {
+    super(props);
+    this.state = { hasError: false, error: null, errorInfo: null };
+  }
+
+  static getDerivedStateFromError(error: Error) {
+    // 当子组件抛出错误时更新 state,用于 fallback UI 的显示
+    return { hasError: true, error: error };
+  }
+
+  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
+    // 这里可以将错误信息上报到日志分析服务
+    console.error('ErrorBoundary caught an error:', error, errorInfo);
+  }
+
+  render() {
+    if (this.state.hasError) {
+      // 这里展示一个简短的 fallback UI
+      return <div>很抱歉,页面加载出现错误。</div>;
+    }
+    return this.props.children;
+  }
+}

+ 67 - 56
src/pages/login/index.tsx

@@ -1,31 +1,31 @@
 /*
  * @Author: your name
  * @Date: 2021-11-09 14:58:08
- * @LastEditTime: 2024-11-15 14:38:05
+ * @LastEditTime: 2025-03-27 14:13:26
  * @LastEditors: code4eat awesomedema@gmail.com
  * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  * @FilePath: /KC-MiddlePlatform/src/pages/login/index.tsx
  */
 
 import React, { useRef, useEffect, useState } from 'react';
-import { Select, notification, Row, Col, Modal } from 'antd';
+import { Select, notification, Row, Col, message } from 'antd';
 import './style.less';
 
 import { useModel, history, Location, Helmet } from 'umi';
 
 import { Form, Input, Button } from 'antd';
 
-import logo from '../../../public/images/kclogo_colorful.png';
-
 import KCSelect from '@/components/kc-select';
 
 import { getHospConfigBySign, getLastLoginSys, login } from '@/service/login';
 
 import { KcimCenterSysId } from '@/constant';
+import { getSysParamsByCode } from '@/service';
+import { ErrorBoundary } from './ErrorBoundary';
 
 const { Option } = Select;
 
-function changeFavicon(src: string) {
+export function changeFavicon(src: string) {
   const link = document.createElement('link');
   const oldLink = document.getElementById('favicon');
   link.id = 'favicon';
@@ -42,11 +42,7 @@ export interface LoginPageType {
   title: string;
 }
 
-export const handleLogin = async (
-  account: string,
-  password: string,
-  hospSign: string | undefined,
-) => {
+export const handleLogin = async (account: string, password: string, hospSign: string | undefined) => {
   if (!hospSign) {
     notification.error({
       message: '网址标记缺失,请检查网址!',
@@ -69,7 +65,16 @@ export const handleLogin = async (
   return null;
 };
 
-const LoginPage: React.FC<LoginPageType> = ({ location, children, title }) => {
+export const getHospType = async () => {
+  const resp = await getSysParamsByCode(KcimCenterSysId, '1864939559301812224');
+  if (resp && resp.length > 0) {
+    return resp[0].value;
+  } else {
+    return undefined;
+  }
+};
+
+const LoginPage: React.FC<Partial<LoginPageType>> = ({ location = { pathname: '/login' } as Location, children, title = '用户登录' }) => {
   const loginPageRef = useRef<HTMLDivElement>(null);
   const [subHospList, setSubHospList] = useState<
     {
@@ -78,10 +83,13 @@ const LoginPage: React.FC<LoginPageType> = ({ location, children, title }) => {
       hospAbbreviation: any;
       name: string;
       value: string | number;
+      loginPic?: string;
+      loginTips?: string;
+      systemName?: string;
     }[]
   >([]); // 分院列表
   const [ifLoading, setIfLoading] = useState(false);
-  const [currentHospName, setCurrentHospName] = useState('欢迎进入医务管理系统');
+  const [currentHospName, setCurrentHospName] = useState(title);
   const { initialState, setInitialState } = useModel('@@initialState');
   const [windowWidth, set_windowWidth] = useState(0);
   const [ifShowLeftBlock, set_ifShowLeftBlock] = useState(true);
@@ -97,7 +105,6 @@ const LoginPage: React.FC<LoginPageType> = ({ location, children, title }) => {
   const getSubHospFunc = async () => {
     if (hospSign) {
       const data = await getHospConfigBySign(hospSign);
-
       if ((data as any) instanceof Array) {
         setSubHospList(
           data.map((t) => {
@@ -112,21 +119,22 @@ const LoginPage: React.FC<LoginPageType> = ({ location, children, title }) => {
               systemName: t.systemName,
             };
             if (t.hospSign == hospSign) {
-              setCurrentHospName(temp.systemName);
+              setCurrentHospName(temp.systemName || title);
               set_currentSelectedSubHop(temp);
               localStorage.setItem('currentSelectedSubHop', JSON.stringify(temp));
               localStorage.setItem('hospAbbreviation', temp.hospAbbreviation);
-              localStorage.setItem('currentHospName', temp.systemName);
+              localStorage.setItem('currentHospName', temp.systemName || '');
             }
             return temp;
-          })
+          }),
         );
       }
+    } else {
+      alert('网址格式不正确,请添加正确的链接hospSign标识!');
     }
   };
 
   const handleResize = (e: Event) => {
-    // console.log('innerWidth',(e.target as Window).innerWidth);
     set_windowWidth((e.target as Window).innerWidth);
   };
 
@@ -135,7 +143,7 @@ const LoginPage: React.FC<LoginPageType> = ({ location, children, title }) => {
   }, [title]);
 
   useEffect(() => {
-    if (windowWidth <= 1000 && windowWidth != 0) {
+    if (windowWidth <= 1000 && windowWidth !== 0) {
       set_ifShowLeftBlock(false);
     } else {
       set_ifShowLeftBlock(true);
@@ -143,37 +151,37 @@ const LoginPage: React.FC<LoginPageType> = ({ location, children, title }) => {
   }, [windowWidth]);
 
   useEffect(() => {
-    if (currentSelectedSubHop) {
-      const { loginPic } = currentSelectedSubHop;
-      if (loginPic.length > 0) {
-        const arr = loginPic.split('|');
-        if (arr.length == 2) {
-          changeFavicon(arr[1]);
-        }
+    if (currentSelectedSubHop && currentSelectedSubHop.loginPic && currentSelectedSubHop.loginPic.length > 0) {
+      const arr = currentSelectedSubHop.loginPic.split('|');
+      if (arr.length === 2) {
+        changeFavicon(arr[1]);
       }
     }
   }, [currentSelectedSubHop]);
 
   useEffect(() => {
     if (hospSign) {
-      location.pathname == '/login' && getSubHospFunc();
+      location.pathname === '/login' && getSubHospFunc();
     }
-  }, [hospSign]);
+  }, [hospSign, location.pathname]);
 
   useEffect(() => {
     // 根据hospSign获取分院信息
-    setInitialState((s:any) => ({ ...s, currentSelectedSys: undefined, openedSysLists: [] }));
+    setInitialState((s: any) => ({ ...(s || {}), currentSelectedSys: undefined, openedSysLists: [] }));
 
     window.addEventListener('resize', (e) => handleResize(e)); // 监听窗口大小改变
 
-    const hospSign = history.location.query?.hospSign as string;
-    localStorage.setItem('hospSign', hospSign);
-    set_hospSign(hospSign);
+    const queryHospSign = history.location.query?.hospSign as string;
+    localStorage.setItem('hospSign', queryHospSign);
+    set_hospSign(queryHospSign);
+    if (!queryHospSign) {
+      message.error('网址格式不正确,请添加正确的链接hospSign标识!');
+    }
 
     return () => {
       window.removeEventListener('resize', handleResize);
     };
-  }, []);
+  }, [setInitialState]);
 
   const onFinish = async (values: { account: string; password: string }) => {
     setIfLoading(true);
@@ -181,8 +189,12 @@ const LoginPage: React.FC<LoginPageType> = ({ location, children, title }) => {
     setIfLoading(false);
 
     if (resp) {
-      const currentSelectedSubHop = JSON.parse(localStorage.getItem('currentSelectedSubHop') || '{}');
-      setInitialState((s: any) => ({ ...s, userData: resp }));
+      const currentSelectedSubHopStr = localStorage.getItem('currentSelectedSubHop') || '{}';
+      const currentSelectedSubHop = JSON.parse(currentSelectedSubHopStr);
+      const customerType = await getHospType();
+      setInitialState((s: any) => ({ ...(s || {}), userData: resp, customerType }));
+      localStorage.setItem('customerType', customerType || '');
+
       if (currentSelectedSubHop.loadType) {
         const lastLoginSysData = await getLastLoginSys({ hospId: currentSelectedSubHop.id, userId: resp.userId });
         if (lastLoginSysData) {
@@ -195,25 +207,25 @@ const LoginPage: React.FC<LoginPageType> = ({ location, children, title }) => {
     }
   };
 
+  const loginPicArr = currentSelectedSubHop?.loginPic ? currentSelectedSubHop.loginPic.split('|') : [];
+  const loginTipsArr = currentSelectedSubHop?.loginTips ? currentSelectedSubHop.loginTips.split('|') : [];
+
   return (
     <div className="loginPage">
       <Helmet>
         <title>{currentHospName}</title>
       </Helmet>
-
-      {location.pathname == '/login' ? (
+      {location.pathname === '/login' ? (
         <>
           <Row style={{ height: '100%' }}>
             {ifShowLeftBlock && (
               <Col flex="500px">
                 <div className="left">
                   <div className="topLogo">
-                    {((currentSelectedSubHop?.loginPic).split('|'))[0] && (
-                      <img className="logo" src={((currentSelectedSubHop?.loginPic).split('|'))[0]} alt="康程智医" />
-                    )}
-                    {((currentSelectedSubHop?.loginTips.split('|'))[0]) && (
-                      <div className="logoDesc" style={{ borderLeft: ((currentSelectedSubHop?.loginPic).split('|'))[0] ? '1px solid #CFD6E6' : 'none' }}>
-                        {(currentSelectedSubHop?.loginTips.split('|'))[0]}
+                    {loginPicArr[0] && <img className="logo" src={loginPicArr[0]} alt="康程智医" />}
+                    {loginTipsArr[0] && (
+                      <div className="logoDesc" style={{ borderLeft: loginPicArr[0] ? '1px solid #CFD6E6' : 'none' }}>
+                        {loginTipsArr[0]}
                       </div>
                     )}
                   </div>
@@ -225,8 +237,8 @@ const LoginPage: React.FC<LoginPageType> = ({ location, children, title }) => {
               <div className="rightLoginArea">
                 {!ifShowLeftBlock && (
                   <div className="topLogo">
-                    <img className="logo" src={currentSelectedSubHop?.loginPic} alt="康程智医" />
-                    <div className="logoDesc">{(currentSelectedSubHop?.loginTips.split('|'))[0]}</div>
+                    {loginPicArr[0] && <img className="logo" src={loginPicArr[0]} alt="康程智医" />}
+                    {loginTipsArr[0] && <div className="logoDesc">{loginTipsArr[0]}</div>}
                   </div>
                 )}
 
@@ -271,20 +283,12 @@ const LoginPage: React.FC<LoginPageType> = ({ location, children, title }) => {
                   </Form.Item>
                 </Form>
                 <div className="bottomCopyright">
-                  {(currentSelectedSubHop?.loginTips.split('|'))[1]}
-                  <span style={{ paddingLeft: 16 }}>{(currentSelectedSubHop?.loginTips.split('|'))[2]}</span>
-                  <a
-                    target="_blank"
-                    href="https://beian.miit.gov.cn/"
-                    style={{ display: 'inline-block', textDecoration: 'none', marginLeft:10,color:'#515866' }}
-                  >
+                  {loginTipsArr[1]}
+                  <span style={{ paddingLeft: 16 }}>{loginTipsArr[2]}</span>
+                  <a target="_blank" href="https://beian.miit.gov.cn/" style={{ display: 'inline-block', textDecoration: 'none', marginLeft: 10, color: '#515866' }} rel="noreferrer">
                     滇ICP备2024031095号
                   </a>
                 </div>
-
-                {/* <div className="beian">
-                 
-                </div> */}
               </div>
             </Col>
           </Row>
@@ -296,4 +300,11 @@ const LoginPage: React.FC<LoginPageType> = ({ location, children, title }) => {
   );
 };
 
-export default LoginPage;
+// 使用ErrorBoundary包裹LoginPage,使得LoginPage内部的错误能被捕获
+const WrappedLoginPage = () => (
+  <ErrorBoundary>
+    <LoginPage />
+  </ErrorBoundary>
+);
+
+export default WrappedLoginPage;

+ 46 - 63
src/pages/platform/_layout.tsx

@@ -1,7 +1,7 @@
 /*
  * @Author: your name
  * @Date: 2022-01-06 15:25:39
- * @LastEditTime: 2024-10-09 15:57:01
+ * @LastEditTime: 2025-03-20 14:55:04
  * @LastEditors: code4eat awesomedema@gmail.com
  * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/_layout.tsx
@@ -22,6 +22,7 @@ import { getAllParams } from '@/service';
 import '../../../public/zhongtaiA';
 import ResizableContainer from '@/components/ResizableContainer';
 import { Menu } from 'antd';
+import EmptyPage from '@/components/emptyPage';
 
 const IconFont = createFromIconfontCN({
   scriptUrl: '',
@@ -33,7 +34,6 @@ interface TransformResult {
   firstLeafNodePath: any[];
 }
 
-
 function transformTree(tree: any[]): TransformResult {
   let firstLeafNode: any | null = null;
   let firstLeafNodePath: any[] = [];
@@ -63,7 +63,7 @@ function transformTree(tree: any[]): TransformResult {
     return newNode;
   }
 
-  const newTree = tree.map(node => traverse(node, []));
+  const newTree = tree.map((node) => traverse(node, []));
 
   return { newTree, firstLeafNode, firstLeafNodePath };
 }
@@ -81,9 +81,7 @@ const findItemByKey: any = (tree: any[], key: string, keyName: string) => {
     }
   }
   return null;
-}
-
-
+};
 
 // 权限检查函数
 function checkAccess(menu: any[], pathname: string) {
@@ -124,7 +122,6 @@ function checkAccess(menu: any[], pathname: string) {
   return false;
 }
 
-
 export default function Layout({ children, location, route, history, match, ...rest }: IRouteComponentProps) {
   const { initialState, setInitialState } = useModel('@@initialState');
   const [openKeys, set_openKeys] = useState<string[]>([]);
@@ -156,6 +153,7 @@ export default function Layout({ children, location, route, history, match, ...r
     set_emptyPageContent(menuItem.description);
   };
 
+  const currentHospName = localStorage.getItem('currentHospName');
 
   const checkPermission = (path: any) => {
     const { navData = [], menuData = [] } = initialState || {};
@@ -166,8 +164,6 @@ export default function Layout({ children, location, route, history, match, ...r
     return hasAccess;
   };
 
-
-
   useEffect(() => {
     const isShowMenu = localStorage.getItem('isChildShowMenu');
     set_isShowPageMenu(isShowMenu == 'true');
@@ -226,7 +222,6 @@ export default function Layout({ children, location, route, history, match, ...r
     return url;
   };
 
-
   const { type, url, contentType } = initialState?.currentSelectedSys ?? {};
   const userData = localStorage.getItem('userData');
   const { token } = JSON.parse(userData as string);
@@ -236,7 +231,7 @@ export default function Layout({ children, location, route, history, match, ...r
   //   if (type == 1 && contentType == 6) {
   //     const userData = localStorage.getItem('userData');
   //     const { token } = JSON.parse(userData as string);
-  //     return <iframe id={'bi_iframe'} style={{ width: '100%', height: '100%', border: 'none' }} src={addTokenToUrl(url as string, token)} 
+  //     return <iframe id={'bi_iframe'} style={{ width: '100%', height: '100%', border: 'none' }} src={addTokenToUrl(url as string, token)}
   //     onLoad={() => adjustIframe()}
 
   //     ></iframe>;
@@ -306,8 +301,6 @@ export default function Layout({ children, location, route, history, match, ...r
     return <>{pageUrl && <iframe id={'bi_iframe'} style={{ width: '100%', height: '100%', border: 'none' }} src={pageUrl} onLoad={() => adjustIframe()}></iframe>};</>;
   }
 
-
-
   // useEffect(() => {
   //   if (!dataLoaded && initialState) {
   //     const navData = initialState.navData || [];
@@ -340,8 +333,6 @@ export default function Layout({ children, location, route, history, match, ...r
     });
   }, [dataLoaded, initialState]);
 
-
-
   return (
     <ProLayout
       style={{
@@ -351,14 +342,14 @@ export default function Layout({ children, location, route, history, match, ...r
       logoStyle={{
         display: 'none',
       }}
-      className='platFormLayout'
+      className="platFormLayout"
       contentStyle={{
-        margin:0,
+        margin: 0,
       }}
       location={{}}
       headerContentRender={false}
       headerRender={false}
-      siderWidth={(isShowPageMenu && noMenu != 'true') ? 200 : 0}
+      siderWidth={isShowPageMenu && noMenu != 'true' ? 200 : 0}
       breakpoint={false}
       pageTitleRender={false}
       disableContentMargin
@@ -403,15 +394,10 @@ export default function Layout({ children, location, route, history, match, ...r
       }}
       menu={{
         autoClose: false,
-        params: {
-          initialState,
-        },
         request: async () => {
-
           if (initialState && initialState.currentSelectedSys) {
-          
-            const { systemId, menuId, path,type,contentType } = initialState.currentSelectedSys;
-            if (systemId || menuId&&(type !=1&&contentType!=6)) {
+            const { systemId, menuId, path, type, contentType } = initialState.currentSelectedSys;
+            if (systemId || (menuId && type != 1 && contentType != 6)) {
               //只有当存在systemId
               const menuData = await getPlatformMenu(systemId || menuId);
               let homePage: TreeItemType | undefined;
@@ -451,7 +437,7 @@ export default function Layout({ children, location, route, history, match, ...r
               };
 
               const _menu = getVFromTree(menuData, 'contentType');
-              setInitialState((t) => ({ ...t, spacicalPageParamsType: _menu, menuData }) as any);
+              setInitialState((t) => ({ ...t, spacicalPageParamsType: _menu, menuData } as any));
 
               if (homePage) {
                 // console.log({homePage});
@@ -493,14 +479,14 @@ export default function Layout({ children, location, route, history, match, ...r
                 arr.map((item: any) =>
                   item.children
                     ? {
-                      ...item,
-                      icon: <FolderOutlined />,
-                      children: addIcon(item.children),
-                    }
+                        ...item,
+                        icon: <FolderOutlined />,
+                        children: addIcon(item.children),
+                      }
                     : {
-                      ...item,
-                      icon: <FileOutlined />,
-                    },
+                        ...item,
+                        icon: <FileOutlined />,
+                      },
                 );
 
               const imgNode = (props: any) => {
@@ -536,45 +522,47 @@ export default function Layout({ children, location, route, history, match, ...r
           return [];
         },
       }}
-
       menuRender={(props: any, defaultDom) => {
         return (
-          <div style={(isShowPageMenu && noMenu != 'true') ? {} : { display: 'inline-block', width: 0, overflow: 'hidden' }}>
+          <div style={isShowPageMenu && noMenu != 'true' ? {} : { display: 'inline-block', width: 0, overflow: 'hidden' }}>
             <ResizableContainer width={collapsed ? 64 : 200} minWidth={0} maxWidth={600} height={'calc(100vh - 48px)'}>
               <div style={{ background: '#fff', height: '100%' }}>
-                <div className='menuWrapper' style={{ height: 'calc(100% - 40px)', overflowY: 'scroll', overflowX: 'hidden', background: '#fff' }}>
+                <div className="menuWrapper" style={{ height: 'calc(100% - 40px)', overflowY: 'scroll', overflowX: 'hidden', background: '#fff' }}>
                   {defaultDom}
                 </div>
 
-                <div style={{
-                  position: 'absolute', zIndex: 10, right: 17, bottom: 9,
-                  display: 'flex', justifyContent: 'center', alignItems: 'center',
-                  cursor: 'pointer', width: 24, height: 24, background: '#fff'
-                }} onClick={
-                  () => {
+                <div
+                  style={{
+                    position: 'absolute',
+                    zIndex: 10,
+                    right: 17,
+                    bottom: 9,
+                    display: 'flex',
+                    justifyContent: 'center',
+                    alignItems: 'center',
+                    cursor: 'pointer',
+                    width: 24,
+                    height: 24,
+                    background: '#fff',
+                  }}
+                  onClick={() => {
                     set_collapsed(collapsed ? false : true);
-                  }
-                }>
+                  }}
+                >
                   <IconFont style={{ display: 'inline-block', fontSize: 24 }} className="menuCollapseIcon" type={collapsed ? 'icon-celanzhankai' : 'icon-celanshouqi'} />
                 </div>
               </div>
-
             </ResizableContainer>
           </div>
-
         );
       }}
-      onPageChange={(location) => { }}
+      onPageChange={(location) => {}}
       layout="side"
       navTheme="light"
       {...moreConfig}
     >
-      {isEmpty && (
-        <div className="emptyContainer" style={{ textAlign: 'center', paddingTop: 100 }}>
-          <h1>{emptyPageContent}</h1>
-        </div>
-      )}
-      {!isEmpty&&( type != 1 && contentType != 6)&& (
+      {isEmpty && <EmptyPage textContent={emptyPageContent} />}
+      {!isEmpty && type != 1 && contentType != 6 && (
         <PageContainer
           className="kcmpPageContainer"
           header={{
@@ -582,11 +570,11 @@ export default function Layout({ children, location, route, history, match, ...r
           }}
           style={{
             margin: 16,
-            marginRight:8
+            marginRight: 8,
           }}
         >
           <Helmet>
-            <title>精益管理中台</title>
+            <title>{initialState?.customerType == '2' ? currentHospName : '精益管理中台'}</title>
           </Helmet>
 
           <div className="page" style={{ height: 'calc(100vh - 80px)', overflowY: 'scroll' }}>
@@ -594,14 +582,9 @@ export default function Layout({ children, location, route, history, match, ...r
           </div>
         </PageContainer>
       )}
-      {
-        type == 1 && contentType == 6 && (
-          <iframe id={'bi_iframe'} style={{ width: '100%', height: '100%', border: 'none' }} src={addTokenToUrl(url as string,token)}
-            onLoad={() => adjustIframe()}
-
-          ></iframe>
-        )
-      }
+      {type == 1 && contentType == 6 && (
+        <iframe id={'bi_iframe'} style={{ width: '100%', height: '100%', border: 'none' }} src={addTokenToUrl(url as string, token)} onLoad={() => adjustIframe()}></iframe>
+      )}
     </ProLayout>
   );
 }

+ 31 - 0
src/pages/platform/home/index.tsx

@@ -0,0 +1,31 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2022-12-14 14:14:32
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-02-28 16:00:08
+ * @FilePath: /BudgetManaSystem/src/pages/Home/index.tsx
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+
+ */
+
+import React from 'react';
+
+import './style.less';
+
+const HomePage: React.FC = () => {
+  return (
+    <div>
+      <div className="HomePage">
+        <div className="title">医务指标管理系统</div>
+        <div className="titleSub">
+          医务指标管理系统是医疗机构实现精细化运营与质量管理的重要数字化工具,系统整合医院HIS、LIS、电子病历等信息系统,通过标准化、动态化的指标管理体系,帮助医院实时监控医疗质量、服务效率及资源使用情况。同时以“指标定义-数据采集-动态监测-干预优化”为闭环,支持用户便捷查看各项医疗指标的定义、计算规则及关联数据来源。通过建立数据驱动的管理闭环,系统有效促进医疗质量持续改进,助力医院实现从经验管理向科学管理的转型升级。
+        </div>
+        <div className="banner">
+          <img src={require('../../../../public/images/indexBgCenter.png')} alt="" />
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default HomePage;

+ 8 - 0
src/pages/platform/home/service.ts

@@ -0,0 +1,8 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2022-12-15 15:14:12
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-02-28 15:32:26
+ * @FilePath: /BudgetManaSystem/src/pages/Home/service.ts
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */

+ 94 - 0
src/pages/platform/home/style.less

@@ -0,0 +1,94 @@
+.HomePage {
+  display: flex;
+  flex-direction: column;
+  height: calc(100vh - 80px);
+  padding: 16px;
+  background: #ffffff;
+  border-radius: 4px;
+  .title {
+    height: 24px;
+    font-weight: bold;
+    font-size: 24px;
+    color: #17181a;
+    line-height: 24px;
+    margin-bottom: 12px;
+    padding-left: 8px;
+    margin-top: 8px;
+  }
+  .titleSub {
+    font-weight: 400;
+    font-size: 14px;
+    color: #525866;
+    line-height: 20px;
+    margin-bottom: 12px;
+    padding: 0 8px;
+  }
+
+  .banner {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    width: 100%;
+    flex-grow: 1;
+    min-height: 440px;
+    // height:calc(100% - 110px);
+    background: url('../../../../public/images/indexBg.png');
+    background-size: cover !important;
+    overflow: hidden;
+    background-repeat: no-repeat !important;
+
+    & > img {
+      width: 810px;
+      transition: all 0.3s linear;
+    }
+  }
+
+  /* 当屏幕宽度小于或等于 600px */
+  @media screen and (max-width: 1250px) {
+    .banner > img {
+      width: 600px;
+    }
+  }
+
+  /* 当屏幕宽度小于或等于 600px */
+  @media screen and (min-width: 1250px) and (max-width: 1360px) {
+    .banner > img {
+      width: 700px;
+    }
+  }
+
+  /* 当屏幕宽度在 601px 到 900px */
+  @media screen and (min-width: 1360px) and (max-width: 1500px) {
+    .banner > img {
+      width: 800px;
+    }
+  }
+
+  /* 当屏幕宽度大于 900px */
+  @media screen and (min-width: 1500px) and (max-width: 1760px) {
+    .banner > img {
+      width: 900px;
+    }
+  }
+
+  /* 当屏幕宽度大于 900px */
+  @media screen and (min-width: 1760px) and (max-width: 1920px) {
+    .banner > img {
+      width: 1000px;
+    }
+  }
+
+  /* 当屏幕宽度大于 900px */
+  @media screen and (min-width: 1960px) and (max-width: 2000px) {
+    .banner > img {
+      width: 1200px;
+    }
+  }
+
+  /* 当屏幕宽度大于 900px */
+  @media screen and (min-width: 2000px) {
+    .banner > img {
+      width: 1400px;
+    }
+  }
+}

+ 42 - 46
src/pages/platform/index.less

@@ -3,14 +3,17 @@
 //     // box-shadow: 2px 0px 6px 0px rgba(54, 61, 77, 0.1);
 //     box-shadow: 12px 12px 2px 1px rgba(0, 0, 255, .2);
 
-
 // }
 
 .platFormLayout {
-  &>.kcmp-ant-layout {
-      flex-direction: row;
+  & > .kcmp-ant-layout {
+    flex-direction: row;
   }
 }
+
+.kcmp-ant-menu-inline .kcmp-ant-menu-item::after {
+  display: none !important;
+}
 .kcmp-ant-pro-sider-light {
   border-right-color: rgba(54, 61, 77, 0.1);
 
@@ -26,58 +29,53 @@
 
       &.kcmp-ant-menu-submenu-selected {
         .kcmp-ant-menu-submenu-arrow {
-          color: #3376FE !important;
+          color: #3376fe !important;
         }
       }
     }
 
     .kcmp-ant-menu-inline {
-
       .kcmp-ant-menu-item {
         border-radius: 4px;
 
         a:hover {
           color: rgba(0, 0, 0, 0.88);
         }
-        
+
         &.kcmp-ant-menu-item-selected {
-          background-color: #F2F6FF !important;
+          background-color: #f2f6ff !important;
           a:hover {
-            color: #3376FE;
+            color: #3376fe;
           }
         }
 
-
         &::after {
           display: none !important;
         }
 
         &:hover {
           color: rgba(0, 0, 0, 0.88) !important;
-          background-color:#f0f2f5;
+          background-color: #f0f2f5;
         }
       }
-
     }
 
-
-    
     &.kcmp-ant-pro-sider-link-menu {
-         .kcmp-ant-menu-item {
-             //height: 24px !important;
-             .collapsedBtn {
-                  display: flex;
-                  justify-content: center;
-                  align-items: center;
-                  cursor: pointer;
-                  position: absolute;
-                  right:8px;
-                  bottom:0px;
-                  z-index: 10;
-                  width: 23px;
-                  height: 23px;
-             }
-         }
+      .kcmp-ant-menu-item {
+        //height: 24px !important;
+        .collapsedBtn {
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          cursor: pointer;
+          position: absolute;
+          right: 8px;
+          bottom: 0px;
+          z-index: 10;
+          width: 23px;
+          height: 23px;
+        }
+      }
     }
 
     &.kcmp-ant-menu-inline-collapsed {
@@ -85,33 +83,31 @@
       .kcmp-ant-menu-item {
         //height: 24px !important;
         .collapsedBtn {
-             display: flex;
-             justify-content: center;
-             align-items: center;
-             cursor: pointer;
-            //  position: absolute;
-            //  right:0px;
-            //  bottom: 0px;
-             z-index: 10;
-             width: 24px;
-             height: 24px;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          cursor: pointer;
+          //  position: absolute;
+          //  right:0px;
+          //  bottom: 0px;
+          z-index: 10;
+          width: 24px;
+          height: 24px;
         }
+      }
     }
-    }
-
-
   }
 
   &.kcmp-ant-layout-sider-collapsed {
-         .kcmp-ant-menu-submenu-selected {
-             background-color:#F2F6FF;
-             border-radius: 8px;
-         }
+    .kcmp-ant-menu-submenu-selected {
+      background-color: #f2f6ff;
+      border-radius: 8px;
+    }
   }
 }
 
 .kcmp-ant-page-header {
-  background-color: #F5F7FA;
+  background-color: #f5f7fa;
 }
 
 .kcmpPageContainer {

+ 48 - 0
src/pages/platform/setting/dataFilling/fillingMana/authHisttory/index.tsx

@@ -0,0 +1,48 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2024-12-06 14:42:57
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-02-27 14:29:06
+ * @FilePath: /MediResourceManaSys/src/pages/qualificationMana/qualificationAuth/authHisttory/index.tsx
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AEi m z z
+ */
+
+import { createFromIconfontCN } from '@ant-design/icons';
+import React, { Children } from 'react';
+import { Timeline } from 'antd';
+import './style.less';
+
+const IconFont = createFromIconfontCN({
+  scriptUrl: '',
+});
+
+export default function AuthHisContent({ hisList }: { hisList: any[] }) {
+  console.log({ hisList });
+
+  const dotNode = (isActive = false) => {
+    return (
+      <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', width: 16, height: 16, border: '2px solid #E7EBF2', borderRadius: '50%' }}>
+        {isActive && <div style={{ width: 8, height: 8, background: '#363F4D', borderRadius: '50%' }}></div>}
+      </div>
+    );
+  };
+  return (
+    <div className="AuthHisContent">
+      <div className="AuthHisContent-title">
+        <IconFont type={'icon-qingliangtishi'} style={{ paddingRight: 12 }} />
+        调整历史
+      </div>
+      <div className="AuthHisContent-content">
+        <Timeline>
+          {hisList.map((a, index) => {
+            return (
+              <Timeline.Item key={index} dot={dotNode(index == 0 ? true : false)}>
+                {a.createTime}调整为{a.value}
+              </Timeline.Item>
+            );
+          })}
+        </Timeline>
+      </div>
+    </div>
+  );
+}

+ 33 - 0
src/pages/platform/setting/dataFilling/fillingMana/authHisttory/style.less

@@ -0,0 +1,33 @@
+.AuthHisContent {
+  width: 260px;
+
+  .AuthHisContent-title {
+    font-weight: 500;
+    font-size: 16px;
+    height: 16px;
+    color: #17181a;
+    line-height: 16px;
+  }
+  .AuthHisContent-content {
+    max-height: 400px;
+    overflow: scroll;
+    overflow-x: hidden;
+    padding-top: 24px;
+
+    .kcmp-ant-timeline-item {
+      &.kcmp-ant-timeline-item-last {
+        padding-bottom: 0;
+      }
+    }
+    .kcmp-ant-timeline-item-tail {
+      left: 8px;
+    }
+    .kcmp-ant-timeline-item-head-custom {
+      top: 4px;
+      left: 9px;
+    }
+    .kcmp-ant-timeline-item-content {
+      margin: 0 0 0 35px;
+    }
+  }
+}

+ 99 - 0
src/pages/platform/setting/dataFilling/fillingMana/generateTableData.tsx

@@ -0,0 +1,99 @@
+import { createFromIconfontCN } from '@ant-design/icons';
+
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2025-02-14 17:50:16
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-02-27 14:42:02
+ * @FilePath: /MediResourceManaSys/src/pages/dataFilling/fillingMana/generateTableData.ts
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+
+import { Drawer, Input, message, Popconfirm, Popover, Switch, Tooltip } from 'antd';
+import AuthHisContent from './authHisttory';
+import { ProColumns } from '@ant-design/pro-table';
+
+type HistoryItem = {
+  value: number;
+  createTime: string;
+};
+
+type ItemDic = {
+  code: string;
+  name: string;
+  type: number;
+  unit: string;
+  status: number;
+  periodType: number;
+  description: string;
+  dimensionType: number;
+};
+
+type ItemData = {
+  code: string;
+  value: any;
+  history?: HistoryItem[];
+  itemDic?: ItemDic;
+  [key: string]: any;
+};
+
+type ItemTitle = {
+  type: number;
+  code: string;
+  name: string;
+};
+
+const IconFont = createFromIconfontCN({
+  scriptUrl: '',
+});
+
+export default function generateTableData(itemTitles: ItemTitle[], itemDatas: ItemData[][], hideIndexs?: number[]) {
+  const columns: any[] = itemTitles.map((title, index) => ({
+    title: title.name,
+    dataIndex: title.code,
+    key: title.code,
+    width: index > 0 ? 120 : 320,
+    valueType: title.type == 2 ? 'digit' : 'text',
+    editable: title.type == 2,
+    ellipsis: true,
+    hideInTable: hideIndexs?.includes(index),
+    columnType: 1,
+    render: (text: any, record: any) => {
+      let history = record[`${title.code}_history`] ? record[`${title.code}_history`] : [];
+      let content = <AuthHisContent hisList={[...history]} />;
+
+      return (
+        <div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center' }}>
+          <span>{text}</span>
+          {history.length > 0 && (
+            <Popover className="qualificationAuth-popover" overlayInnerStyle={{ borderRadius: 4 }} content={content} title={false}>
+              <IconFont type={'icon-qingliangtishi'} style={{ marginLeft: 10 }} />
+            </Popover>
+          )}
+        </div>
+      ); // 返回 text 对应的数据
+    },
+  }));
+
+  const dataSource = itemDatas.map((row, rowIndex) => {
+    // 用 Record<string, any> 类型来解决动态属性问题
+    const rowData: Record<string, any> = { id: `row_${rowIndex}` };
+
+    row.forEach((item) => {
+      // 处理每个对象的 `value`
+      rowData[item.code] = item.value;
+
+      // 遍历当前 item 的所有属性
+      Object.keys(item).forEach((key) => {
+        if (key !== 'code' && key !== 'value') {
+          // 排除 code 和 value
+          rowData[`${item.code}_${key}`] = item[key]; // 将其他属性添加到 rowData 中
+        }
+      });
+    });
+
+    return rowData;
+  });
+
+  return { columns, dataSource };
+}

+ 652 - 0
src/pages/platform/setting/dataFilling/fillingMana/index.tsx

@@ -0,0 +1,652 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2023-03-03 11:30:33
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-02-28 15:18:53
+ * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/index.tsx
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+
+import { createFromIconfontCN } from '@ant-design/icons';
+
+import { ModalForm, ProFormDependency, ProFormInstance, ProFormRadio, ProFormSelect, ProFormText, ProFormTextArea } from '@ant-design/pro-form';
+import { ActionType, ProColumns } from '@ant-design/pro-table';
+import { Input, message, Popconfirm, Switch, Tooltip } from 'antd';
+import { useEffect, useRef, useState } from 'react';
+
+import 'moment/locale/zh-cn';
+
+import { addData, applyBatchItemValueReq, delData, editData, generateTableDataReq, getCurrentHospAlldeps, getTableList } from './service';
+
+import './style.less';
+
+import React from 'react';
+import generateTableData from './generateTableData';
+import { useModel } from 'umi';
+import KCTable from '@/components/kcTable';
+
+const IconFont = createFromIconfontCN({
+  scriptUrl: '',
+});
+
+//填报周期
+export const DATAFILL_PERIODTYPE = [
+  { label: '月度', value: 1 },
+  { label: '季度', value: 2 },
+  { label: '年度', value: 3 },
+  { label: '变动时', value: 4 },
+];
+//填报主体类型
+export const DATAFILL_MAINTYPE = [
+  { label: '职能部门', value: 1 },
+  { label: '相关科室', value: 2 },
+];
+
+//填报维度
+export const DATAFILL_DIMENSIONTYPE = [
+  { label: '按全院  ', value: 1 },
+  { label: '按科室', value: 2 },
+];
+
+type Data = {
+  [key: number]: string;
+};
+
+type LabelValue = {
+  label: string;
+  value: number;
+};
+
+export function transformToLabelValue(data: Data): LabelValue[] {
+  const result: LabelValue[] = [];
+  for (const key in data) {
+    if (data.hasOwnProperty(key)) {
+      result.push({
+        label: data[key],
+        value: parseInt(key),
+      });
+    }
+  }
+  return result;
+}
+
+export const searchTree = (tree: any[], searchTerm: string) => {
+  const searchTermLower = searchTerm.toLowerCase();
+
+  function searchNode(node: any) {
+    const matchedChildren: any[] = [];
+
+    if (node.children && node.children.length > 0) {
+      node.children.forEach((child: any) => {
+        const matchedChild = searchNode(child);
+        if (matchedChild) {
+          matchedChildren.push(matchedChild);
+        }
+      });
+    }
+
+    if (node.responsibilityName.toLowerCase().includes(searchTermLower) || (node.responsibilityCode && node.responsibilityCode.toLowerCase().includes(searchTermLower))) {
+      return {
+        ...node,
+        children: matchedChildren.length > 0 ? matchedChildren : node.children,
+      };
+    } else if (matchedChildren.length > 0) {
+      return {
+        ...node,
+        children: matchedChildren,
+      };
+    }
+
+    return null;
+  }
+
+  return tree.map((rootNode) => searchNode(rootNode)).filter((node) => node !== null);
+};
+
+const defaultYear = new Date().getFullYear();
+let table_columns: any[] = [];
+let total_tableDataSource: any[] = [];
+
+export default function FillingMana() {
+  const { initialState, setInitialState } = useModel('@@initialState');
+  const [tableDataFilterParams, set_tableDataFilterParams] = useState<any | undefined>();
+  const [tableDataSearchKeywords, set_tableDataSearchKeywords] = useState<string>('');
+  const tableRef = useRef<ActionType>();
+  const detailTableRef = useRef<ActionType>();
+  const formRef = useRef<ProFormInstance>();
+  const [tableDataSource, set_tableDataSource] = useState<any[]>([]);
+  const [tableColumns, set_tableColumns] = useState<ProColumns[]>([]);
+  const [currentEditRow, set_currentEditRow] = useState<any>(undefined);
+  const [actionType, set_actionType] = useState<'NORMAL' | 'DETAIL' | undefined>('NORMAL');
+  const [currentEditTableRow, set_currentEditTableRow] = useState<any>(undefined);
+  const [currentYear, set_currentYear] = useState(defaultYear);
+  const [isFirstLoad, setIsFirstLoad] = useState(true);
+
+  const tableColumns_default: ProColumns[] = [
+    {
+      title: '操作',
+      key: 'option',
+      fixed: 'right',
+      width: 80,
+      valueType: 'option',
+      render: (_: any, record: any) => {
+        return (
+          <a
+            key={'edit'}
+            onClick={() => {
+              set_currentEditTableRow(record);
+            }}
+          >
+            编辑
+          </a>
+        );
+      },
+    },
+  ];
+
+  const columns: ProColumns[] = [
+    {
+      title: '填报项目',
+      ellipsis: true,
+      dataIndex: 'name',
+    },
+    {
+      title: '填报周期',
+      ellipsis: true,
+      dataIndex: 'periodType',
+      renderText(code, record, index, action) {
+        const item = DATAFILL_PERIODTYPE.find((item) => item.value === code);
+        return item ? item.label : '';
+      },
+    },
+    {
+      title: '填报维度',
+      ellipsis: true,
+      dataIndex: 'dimensionType',
+      renderText(code, record, index, action) {
+        const item = DATAFILL_DIMENSIONTYPE.find((item) => item.value === code);
+        return item ? item.label : '';
+      },
+    },
+    {
+      title: '填报主体类型',
+      ellipsis: true,
+      dataIndex: 'type',
+      renderText(code, record, index, action) {
+        const item = DATAFILL_MAINTYPE.find((item) => item.value === code);
+        return item ? item.label : '';
+      },
+    },
+    {
+      title: '填报主体',
+      ellipsis: true,
+      dataIndex: 'typeName',
+    },
+    {
+      title: '填报单位',
+      ellipsis: true,
+      dataIndex: 'unit',
+    },
+    {
+      title: '填报说明',
+      ellipsis: true,
+      dataIndex: 'description',
+    },
+    {
+      title: '启用',
+      width: 60,
+      ellipsis: true,
+      dataIndex: 'status',
+      renderText(num, record) {
+        return (
+          <Switch
+            size="small"
+            checked={num}
+            onChange={(bool) =>
+              updateTable(
+                {
+                  ...record,
+                  status: bool ? 1 : 0,
+                  fillDeptList: record?.fillDeptList.map((a: any) => ({ value: a.departmentCode, label: a.departmentName })),
+                  relateDeptList: record?.relateDeptList.map((a: any) => ({ value: a.departmentCode, label: a.departmentName })),
+                },
+                'EDIT',
+              )
+            }
+          />
+        );
+      },
+    },
+    {
+      title: '操作',
+      key: 'option',
+      fixed: 'right',
+      width: 120,
+      valueType: 'option',
+      render: (_: any, record: any) => {
+        const { applyStatus } = record;
+
+        return [
+          <a
+            key={'detail'}
+            onClick={() => {
+              set_currentEditRow(record);
+            }}
+          >
+            详情
+          </a>,
+          <UpDataActBtn record={record} type={'EDIT'} />,
+          <Popconfirm title="是否确认删除?" key="del" onConfirm={() => delTableData(record)}>
+            <a>删除</a>
+          </Popconfirm>,
+        ];
+      },
+    },
+  ];
+
+  const getGenerateTableData = async () => {
+    const resp = await generateTableDataReq({ fillItemCode: currentEditRow?.code, dataYear: currentYear });
+    if (resp) {
+      const { itemTitles, itemDatas } = resp;
+      const { columns, dataSource } = generateTableData(itemTitles, itemDatas, [0]);
+      table_columns = columns;
+      total_tableDataSource = dataSource;
+      set_tableColumns(columns);
+      set_tableDataSource(dataSource);
+    }
+  };
+  const getTableData = async (params: any) => {
+    const resp = await getTableList({ ...params });
+    if (resp) {
+      return {
+        data: resp.list,
+        success: true,
+        total: resp.totalCount,
+        pageSize: resp.pageSize,
+        totalPage: resp.totalPage,
+      };
+    }
+    return [];
+  };
+
+  const delTableData = async (record: any) => {
+    const resp = await delData(record.code);
+    if (resp) {
+      tableRef.current?.reload();
+    }
+  };
+
+  const updateTable = async (data: any, type: 'ADD' | 'EDIT') => {
+    try {
+      const result = {
+        name: data.name,
+        type: data.type,
+        dimensionType: data.type == 2 ? 2 : data.dimensionType,
+        periodType: data.periodType,
+        unit: data.unit,
+        status: type == 'ADD' ? null : data.status,
+        description: data.description,
+        fillDeptList:
+          data.fillDeptList instanceof Array
+            ? data.fillDeptList.map((a: any) => ({ departmentCode: a.value, departmentName: a.label }))
+            : [{ departmentCode: data.fillDeptList.value, departmentName: data.fillDeptList.label }],
+        relateDeptList: data.type != 2 && data.dimensionType != 1 && data.relateDeptList ? data.relateDeptList.map((a: any) => ({ departmentCode: a.value, departmentName: a.label })) : [],
+      };
+
+      if (type == 'ADD') {
+        const resp = await addData(result);
+        if (resp) {
+          tableRef.current?.reload();
+          return true;
+        }
+      }
+      if (type == 'EDIT') {
+        const resp = await editData({ code: data.code, ...result });
+        if (resp) {
+          tableRef.current?.reload();
+        }
+        return true;
+      }
+    } catch (error) {
+      console.log({ 'updateTable error': error });
+    }
+
+    return false;
+  };
+
+  const tableDataSearchHandle = (paramName: string) => {
+    set_tableDataFilterParams({
+      ...tableDataFilterParams,
+      current: 1,
+      [`${paramName}`]: tableDataSearchKeywords,
+    });
+  };
+
+  const detailtableDataSearchHandle = (paramName: string) => {
+    const result = total_tableDataSource.filter((a: any) => a[`${paramName}`].indexOf(tableDataSearchKeywords) != -1);
+    set_tableDataSource(result);
+  };
+
+  const UpDataActBtn = ({ record, type, sideHandle }: { record: any; type: 'EDIT' | 'ADD'; sideHandle?: { [key: string]: any } }) => {
+    const actType = type;
+    const ref = React.createRef<{ save: any }>();
+    const [maintype, set_maintype] = useState(undefined);
+    const [dimensionType, set_dimensionType] = useState(undefined);
+    const { commitResult } = sideHandle ? sideHandle : {};
+
+    useEffect(() => {
+      if (type !== undefined) {
+        formRef.current?.setFieldValue('fillDeptList', []); // 清空填报主体
+      }
+    }, [maintype]);
+
+    useEffect(() => {
+      if (dimensionType !== undefined) {
+        formRef.current?.setFieldValue('relateDeptList', []); // 清空填相关科室
+      }
+    }, [dimensionType]);
+
+    return (
+      <ModalForm
+        title={`${type == 'EDIT' ? '编辑' : '新增'}`}
+        width={352}
+        formRef={formRef}
+        initialValues={
+          type == 'EDIT'
+            ? {
+                ...record,
+                fillDeptList: record?.fillDeptList?.map((a: any) => ({ label: a.departmentName, value: Number(a.departmentCode) })) || [],
+                relateDeptList: record?.relateDeptList?.map((a: any) => ({ label: a.departmentName, value: Number(a.departmentCode) })) || [],
+              }
+            : { periodType: 1, type: 1, dimensionType: 1 }
+        }
+        trigger={type == 'EDIT' ? <a key="edit">编辑</a> : <a className="add">新增</a>}
+        onFinish={(val) => {
+          commitResult && commitResult();
+          return updateTable(type == 'EDIT' ? { ...val, status: record.status } : { ...val }, type);
+        }}
+        modalProps={{ destroyOnClose: true }}
+        colProps={{ span: 24 }}
+        grid
+      >
+        {
+          <>
+            <ProFormText
+              name="name"
+              label="填报项目:"
+              placeholder="请输入"
+              // disabled={type == 'EDIT'}
+              rules={[{ required: true, message: '填报项目不能为空!' }]}
+            />
+            <ProFormText name="code" label="填报编码:" hidden={type == 'ADD'} disabled={type == 'EDIT'} />
+            <ProFormRadio.Group label="填报周期:" name="periodType" disabled={type == 'EDIT' && record.limitFlag} options={DATAFILL_PERIODTYPE} rules={[{ required: true }]} />
+            <ProFormRadio.Group
+              label="填报主体类型:"
+              name="type"
+              disabled={type == 'EDIT' && record.limitFlag}
+              options={DATAFILL_MAINTYPE}
+              rules={[{ required: true }]}
+              fieldProps={{
+                onChange(e) {
+                  set_maintype(e.target.value);
+                },
+              }}
+            />
+            <ProFormDependency name={['type']}>
+              {({ type }) => {
+                return (
+                  <>
+                    <ProFormSelect
+                      label="填报主体:"
+                      name="fillDeptList"
+                      disabled={actType == 'EDIT' && record.limitFlag}
+                      fieldProps={
+                        type === 2
+                          ? {
+                              mode: 'tags',
+                              maxTagCount: 'responsive',
+                              labelInValue: true,
+                              size: 'small',
+                            }
+                          : { labelInValue: true, size: 'small' }
+                      }
+                      rules={[{ required: true, message: '填报主体不能为空!' }]}
+                      request={async () => {
+                        const depLists = await getCurrentHospAlldeps();
+                        if (depLists) {
+                          return transformToLabelValue(depLists);
+                        }
+                        return [];
+                      }}
+                    />
+
+                    {type != 2 && (
+                      <>
+                        <ProFormRadio.Group label="填报维度:" name="dimensionType" disabled={type == 'EDIT' && record.limitFlag} options={DATAFILL_DIMENSIONTYPE} rules={[{ required: true }]} />
+                        <ProFormDependency name={['dimensionType']}>
+                          {({ dimensionType }) => {
+                            return (
+                              dimensionType == 2 && (
+                                <ProFormSelect
+                                  label="相关科室:"
+                                  disabled={type == 'EDIT' && record.limitFlag}
+                                  name={'relateDeptList'}
+                                  fieldProps={{
+                                    mode: 'tags',
+                                    maxTagCount: 'responsive',
+                                    labelInValue: true,
+                                    size: 'small',
+                                  }}
+                                  rules={[{ required: true, message: '相关科室不能为空!' }]}
+                                  request={async () => {
+                                    const depLists = await getCurrentHospAlldeps();
+                                    if (depLists) {
+                                      return transformToLabelValue(depLists);
+                                    }
+                                    return [];
+                                  }}
+                                />
+                              )
+                            );
+                          }}
+                        </ProFormDependency>
+                      </>
+                    )}
+                  </>
+                );
+              }}
+            </ProFormDependency>
+
+            <ProFormText name="unit" label="填报值单位:" placeholder="请输入" rules={[{ required: true, message: '填报值单位不能为空!' }]} />
+            <ProFormTextArea
+              name="description"
+              label="填报说明:"
+              placeholder="请输入"
+              fieldProps={{
+                rows: 4,
+              }}
+              rules={[{ required: false }]}
+            />
+          </>
+        }
+      </ModalForm>
+    );
+  };
+
+  useEffect(() => {
+    if (!isFirstLoad) {
+      getGenerateTableData();
+    } else {
+      setIsFirstLoad(false); // 标记首次加载已完成
+    }
+  }, [currentYear]);
+
+  useEffect(() => {
+    if (currentEditRow) {
+      set_actionType('DETAIL');
+      getGenerateTableData();
+    } else {
+      set_actionType('NORMAL');
+    }
+  }, [currentEditRow]);
+
+  return (
+    <div className="FillingMana">
+      {actionType == 'DETAIL' && (
+        <div className="FillingMana-detail">
+          <div className="FillingMana-detail-header">
+            <div className="FillingMana-detail-header-title">
+              <div className="backBtn" onClick={() => set_currentEditRow(undefined)}>
+                <IconFont style={{ fontSize: 15 }} type={'iconfanhui'} />
+              </div>
+              <span>{currentEditRow?.name}</span>
+            </div>
+            <div className="FillingMana-detail-header-title-sub">
+              <span>
+                填报周期:
+                {currentEditRow &&
+                  `${DATAFILL_PERIODTYPE.find((item) => item.value === currentEditRow.periodType) ? DATAFILL_PERIODTYPE.find((item) => item.value === currentEditRow.periodType)?.label : ''}`}
+              </span>
+              <span>
+                填报维度:
+                {currentEditRow &&
+                  `${
+                    DATAFILL_DIMENSIONTYPE.find((item) => item.value === currentEditRow.dimensionType) ? DATAFILL_DIMENSIONTYPE.find((item) => item.value === currentEditRow.dimensionType)?.label : ''
+                  }`}
+              </span>
+              <span>填报单位:{currentEditRow?.unit}</span>
+            </div>
+          </div>
+          <div className="FillingMana-detail-content">
+            <div className="toolBar">
+              <div className="filter">
+                <div className="filterItem">
+                  <span className="label" style={{ whiteSpace: 'nowrap' }}>
+                    {' '}
+                    检索:
+                  </span>
+                  <Input
+                    placeholder={'填报科室'}
+                    allowClear
+                    autoComplete="off"
+                    suffix={<IconFont type="iconsousuo" style={{ color: '#99A6BF' }} onClick={() => detailtableDataSearchHandle('dimensionName')} />}
+                    onChange={(e) => {
+                      set_tableDataSearchKeywords(e.target.value);
+                      if (e.target.value.length == 0) {
+                        set_tableDataSource(total_tableDataSource);
+                      }
+                    }}
+                    onPressEnter={(e: any) => {
+                      const result = total_tableDataSource.filter((a: any) => a.dimensionName.indexOf(e.target.value) != -1);
+                      set_tableDataSource(result);
+                    }}
+                  />
+                </div>
+              </div>
+              <div className="btnGroup">
+                {(currentEditRow?.periodType == 1 || currentEditRow?.periodType == 2) && (
+                  <div className="changeBtn">
+                    <div className="actBtn" onClick={() => set_currentYear(currentYear - 1)}>
+                      <IconFont type={'iconshuangzuo'} />
+                    </div>
+                    <span style={{ padding: '0 8px' }}>{currentYear}</span>
+                    <div className="actBtn" onClick={() => set_currentYear(currentYear + 1)}>
+                      <IconFont type={'iconshuangyou'} />
+                    </div>
+                  </div>
+                )}
+              </div>
+            </div>
+            <KCTable
+              newVer
+              actionRef={detailTableRef}
+              columns={[...tableColumns, ...tableColumns_default]}
+              scroll={{ y: 'calc(100vh - 298px)', x: `${tableColumns.length * 120}px` }}
+              rowKey="id"
+              dataSource={tableDataSource}
+              pagination={false}
+              editable={{
+                editableKeys: currentEditTableRow ? [currentEditTableRow.id] : [],
+                actionRender: (row, config, defaultDom) => [defaultDom.save, defaultDom.cancel],
+                onSave: async (key, record, originRow, newLineConfig) => {
+                  let unEditableColum: any = [];
+                  const editableColumn = table_columns.filter((a: any) => {
+                    !a.editable && unEditableColum.push(a);
+                    return a.editable;
+                  });
+
+                  const result = editableColumn.map((a: any) => ({
+                    fillItemCode: record[`${unEditableColum[0].dataIndex}_fillItemCode`],
+                    dimensionCode: record[`${unEditableColum[0].dataIndex}_dimensionCode`],
+                    periodCode: a.key,
+                    value: record[`${a.key}`] ?? null,
+                    dataYear: currentYear,
+                  }));
+                  const resp = await applyBatchItemValueReq(result);
+                  if (resp) {
+                    set_currentEditTableRow(undefined);
+                    getGenerateTableData();
+                  }
+                  return Promise.resolve(true);
+                },
+                onCancel: (key, record, originRow, newLineConfig) => {
+                  set_currentEditTableRow(undefined);
+                  return Promise.resolve(true);
+                },
+              }}
+            />
+          </div>
+        </div>
+      )}
+      {actionType != 'DETAIL' && (
+        <div style={{ padding: 16, background: '#fff', borderRadius: 4 }}>
+          <div className="toolBar">
+            <div className="filter">
+              <div className="filterItem">
+                <span className="label" style={{ whiteSpace: 'nowrap' }}>
+                  {' '}
+                  检索:
+                </span>
+                <Input
+                  placeholder={'填报项目'}
+                  allowClear
+                  autoComplete="off"
+                  suffix={<IconFont type="iconsousuo" style={{ color: '#99A6BF' }} onClick={() => tableDataSearchHandle('name')} />}
+                  onChange={(e) => {
+                    set_tableDataSearchKeywords(e.target.value);
+                    if (e.target.value.length == 0) {
+                      set_tableDataFilterParams({
+                        ...tableDataFilterParams,
+                        current: 1,
+                        name: '',
+                      });
+                    }
+                  }}
+                  onPressEnter={(e) => {
+                    set_tableDataFilterParams({
+                      ...tableDataFilterParams,
+                      current: 1,
+                      name: (e.target as HTMLInputElement).value,
+                    });
+                  }}
+                />
+              </div>
+            </div>
+            <div className="btnGroup">
+              <UpDataActBtn record={undefined} type={'ADD'} />
+            </div>
+          </div>
+          <KCTable
+            newVer
+            columns={columns as ProColumns[]}
+            scroll={{ y: 'calc(100vh - 232px)' }}
+            actionRef={tableRef}
+            rowKey="id"
+            params={tableDataFilterParams}
+            request={(params) => getTableData(params)}
+          />
+        </div>
+      )}
+    </div>
+  );
+}

+ 73 - 0
src/pages/platform/setting/dataFilling/fillingMana/service.ts

@@ -0,0 +1,73 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2023-03-03 16:31:27
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-02-19 10:02:52
+ * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/service.ts
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+
+import { request } from 'umi';
+
+//获取table列表数据
+
+export const getTableList = (params?: any) => {
+  return request('/centerSys/datafill/getPageItemList', {
+    method: 'GET',
+    params: { ...params },
+  });
+};
+
+//获取当前院区所有科室
+export const getCurrentHospAlldeps = () => {
+  return request('/centerSys/sysdepartment/getHospDepartment', {
+    method: 'GET',
+  });
+};
+
+//获取动态表格数据
+export const generateTableDataReq = (params: { fillItemCode: string; dataYear: number; name?: string }) => {
+  return request('/centerSys/datafill/getItemDetail', {
+    method: 'GET',
+    params: { ...params },
+  });
+};
+
+//新增
+export type AddTableDataType = {};
+
+export const addData = (data: AddTableDataType) => {
+  return request('/centerSys/datafill/addItem', {
+    method: 'POST',
+    data,
+  });
+};
+
+//编辑表格数据
+
+export type TableRowEditType = {
+  id: number;
+} & AddTableDataType;
+
+export const editData = (data: any) => {
+  return request(`/centerSys/datafill/updateItem`, {
+    method: 'POST',
+    data,
+  });
+};
+
+//删除表格操作
+export const delData = (code: string) => {
+  return request('/centerSys/datafill/deleteItem', {
+    method: 'POST',
+    params: { code },
+  });
+};
+
+//批量保存填报详情
+export const applyBatchItemValueReq = (data: any) => {
+  return request(`/centerSys/datafill/applyBatchItemValue`, {
+    method: 'POST',
+    data,
+  });
+};

+ 163 - 0
src/pages/platform/setting/dataFilling/fillingMana/style.less

@@ -0,0 +1,163 @@
+.FillingMana {
+  // padding: 16px;
+  // background: #FFFFFF;
+  // border-radius: 4px;
+
+  .toolBar {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 16px;
+
+    .filter {
+      display: flex;
+      flex-direction: row;
+      justify-content: flex-start;
+      align-items: center;
+
+      .filterItem {
+        display: flex;
+        flex-direction: row;
+        justify-content: center;
+        align-items: center;
+      }
+    }
+
+    .btnGroup {
+      .import,
+      .export {
+        cursor: pointer;
+        display: inline-block;
+        font-size: 14px;
+        font-weight: 400;
+        color: #17181a;
+        line-height: 24px;
+        padding: 0 14px;
+        margin-right: 8px;
+        background: #fafcff;
+        border-radius: 4px;
+        border: 1px solid #dae2f2;
+      }
+
+      .add {
+        cursor: pointer;
+        display: inline-block;
+        font-size: 14px;
+        font-weight: 400;
+        color: #ffffff;
+        line-height: 24px;
+        padding: 0 14px;
+        background: #3377ff;
+        border-radius: 4px;
+      }
+
+      .changeBtn {
+        display: flex;
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: center;
+
+        .actBtn {
+          cursor: pointer;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          width: 24px;
+          height: 24px;
+          background: #fafcff;
+          border-radius: 4px;
+          border: 1px solid #dae2f2;
+        }
+      }
+    }
+  }
+
+  .FillingMana-detail {
+    .FillingMana-detail-header {
+      height: 80px;
+      padding: 16px;
+      background: #ffffff;
+      border-radius: 4px;
+      margin-bottom: 16px;
+
+      .FillingMana-detail-header-title {
+        display: flex;
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: center;
+        margin-bottom: 10px;
+
+        .backBtn {
+          cursor: pointer;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          width: 24px;
+          height: 24px;
+          background: #fafcff;
+          border-radius: 4px;
+          margin-right: 12px;
+          border: 1px solid #dae2f2;
+        }
+
+        & > span {
+          font-weight: bold;
+          font-size: 20px;
+          color: #17181a;
+          height: 20px;
+          line-height: 20px;
+        }
+      }
+
+      .FillingMana-detail-header-title-sub {
+        display: flex;
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: center;
+        padding-left: 36px;
+
+        & > span {
+          display: inline-block;
+          height: 14px;
+          line-height: 14px;
+          font-weight: 400;
+          font-size: 14px;
+          color: #525866;
+          margin-right: 24px;
+        }
+      }
+    }
+
+    .FillingMana-detail-content {
+      padding: 16px;
+      background: #ffffff;
+      border-radius: 4px;
+
+      .toolBar {
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 16px;
+
+        .filter {
+          display: flex;
+          flex-direction: row;
+          justify-content: flex-start;
+          align-items: center;
+
+          .filterItem {
+            display: flex;
+            flex-direction: row;
+            justify-content: center;
+            align-items: center;
+          }
+        }
+
+        .btnGroup {
+        }
+      }
+    }
+  }
+}

+ 48 - 0
src/pages/platform/setting/dataFilling/mineFilling/authHisttory/index.tsx

@@ -0,0 +1,48 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2024-12-06 14:42:57
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-02-28 14:14:41
+ * @FilePath: /MediResourceManaSys/src/pages/qualificationMana/qualificationAuth/authHisttory/index.tsx
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AEi m z z
+ */
+
+import { createFromIconfontCN } from '@ant-design/icons';
+import React, { Children } from 'react';
+import { Timeline } from 'antd';
+import './style.less';
+
+const IconFont = createFromIconfontCN({
+  scriptUrl: '',
+});
+
+export default function AuthHisContent({ hisList }: { hisList: any[] }) {
+  console.log({ hisList });
+
+  const dotNode = (isActive = false) => {
+    return (
+      <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', width: 16, height: 16, border: '2px solid #E7EBF2', borderRadius: '50%' }}>
+        {isActive && <div style={{ width: 8, height: 8, background: '#363F4D', borderRadius: '50%' }}></div>}
+      </div>
+    );
+  };
+  return (
+    <div className="AuthHisContent">
+      <div className="AuthHisContent-title">
+        <IconFont type={'icon-qingliangtishi'} style={{ paddingRight: 12 }} />
+        调整历史
+      </div>
+      <div className="AuthHisContent-content">
+        <Timeline>
+          {hisList.map((a, index) => {
+            return (
+              <Timeline.Item key={index} dot={dotNode(index == 0 ? true : false)}>
+                {a.createTime}调整为{a.value}
+              </Timeline.Item>
+            );
+          })}
+        </Timeline>
+      </div>
+    </div>
+  );
+}

+ 33 - 0
src/pages/platform/setting/dataFilling/mineFilling/authHisttory/style.less

@@ -0,0 +1,33 @@
+.AuthHisContent {
+  width: 400px;
+
+  .AuthHisContent-title {
+    font-weight: 500;
+    font-size: 16px;
+    height: 16px;
+    color: #17181a;
+    line-height: 16px;
+  }
+  .AuthHisContent-content {
+    max-height: 400px;
+    overflow: scroll;
+    overflow-x: hidden;
+    padding-top: 24px;
+
+    .mrms-ant-timeline-item {
+      &.mrms-ant-timeline-item-last {
+        padding-bottom: 0;
+      }
+    }
+    .mrms-ant-timeline-item-tail {
+      left: 8px;
+    }
+    .mrms-ant-timeline-item-head-custom {
+      top: 0.5px;
+      left: 9px;
+    }
+    .mrms-ant-timeline-item-content {
+      margin: 0 0 0 35px;
+    }
+  }
+}

+ 261 - 0
src/pages/platform/setting/dataFilling/mineFilling/generateTableData.tsx

@@ -0,0 +1,261 @@
+import { createFromIconfontCN } from '@ant-design/icons';
+
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2025-02-14 17:50:16
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-02-28 14:45:09
+ * @FilePath: /MediResourceManaSys/src/pages/dataFilling/fillingMana/generateTableData.ts
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+
+import { Input, Modal, Popover, Tooltip } from 'antd';
+import AuthHisContent from './authHisttory';
+import { ProFormDigit } from '@ant-design/pro-form';
+import { useState } from 'react';
+
+type HistoryItem = {
+  value: number;
+  createTime: string;
+};
+
+type ItemDic = {
+  code: string;
+  name: string;
+  type: number;
+  unit: string;
+  status: number;
+  periodType: number;
+  description: string;
+  dimensionType: number;
+};
+
+type ItemData = {
+  code: string;
+  value: any;
+  history?: HistoryItem[];
+  itemDic?: ItemDic;
+  [key: string]: any;
+};
+
+type ItemTitle = {
+  currentPeriod: any;
+  type: number;
+  code: string;
+  name: string;
+};
+
+const IconFont = createFromIconfontCN({
+  scriptUrl: '',
+});
+
+// 获取当前日期
+const currentDate = new Date();
+
+// 获取当前月份
+const currentMonth = currentDate.getMonth();
+
+// 获取上一个月的月份(注意:getMonth() 返回的是 0-11 的值,所以需要减去 1)
+const _lastMonth = currentMonth === 0 ? 11 : currentMonth - 1;
+const lastMonth = _lastMonth + 1; //加 1 是为了将月份转换为 1-12 的格式
+
+function getPreviousQuarter() {
+  const date = new Date();
+  const currentMonth = date.getMonth();
+  let previousQuarter;
+
+  // 根据当前月份确定上一个季度
+  if (currentMonth >= 0 && currentMonth <= 2) {
+    previousQuarter = 4; // 当前是第一季度,上一个季度是第四季度
+  } else if (currentMonth >= 3 && currentMonth <= 5) {
+    previousQuarter = 1; // 当前是第二季度,上一个季度是第一季度
+  } else if (currentMonth >= 6 && currentMonth <= 8) {
+    previousQuarter = 2; // 当前是第三季度,上一个季度是第二季度
+  } else {
+    previousQuarter = 3; // 当前是第四季度,上一个季度是第三季度
+  }
+
+  return previousQuarter;
+}
+
+function getPreviousYear() {
+  const date = new Date();
+  const currentYear = date.getFullYear();
+
+  // 返回上一年度的年份
+  return currentYear - 1;
+}
+
+export default function generateTableData(itemTitles: ItemTitle[], itemDatas: ItemData[][], sideHandle: { [key: string]: any }) {
+  const { disabledState, setDisabledState, updateTableHandle, currentTab } = sideHandle;
+
+  const columns: any[] = itemTitles.map((title, index) => ({
+    title: title.name,
+    dataIndex: title.code,
+    key: title.code,
+    width: index > 0 ? 120 : 320,
+    editable: title.type == 2,
+    valueType: title.type == 2 ? 'digit' : 'text',
+    fixed: title.code == 'fillItemName' || title.code == 'dimensionName' ? 'left' : false,
+    columnType: 1,
+    render: (val: any, record: any) => {
+      let editVal: number | string | null = null;
+      const itemDicStr = record[`${title.code}_itemDic`] ? record[`${title.code}_itemDic`].description : '';
+      const isDisabled = typeof disabledState[`${title.code}_${record.id}`] == 'boolean' ? disabledState[`${title.code}_${record.id}`] : true;
+
+      let history = record[`${title.code}_history`] ? record[`${title.code}_history`] : [];
+      let content = <AuthHisContent hisList={[...history]} />;
+
+      let flag = title.currentPeriod;
+
+      return flag ? (
+        <>
+          {flag ? (
+            <div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center' }}>
+              <ProFormDigit
+                disabled={isDisabled}
+                noStyle
+                width={80}
+                placeholder={''}
+                fieldProps={{
+                  defaultValue: typeof val == 'object' ? val.props.text : val,
+                  onChange(value) {
+                    editVal = value;
+                  },
+                }}
+              />
+              <div
+                style={{
+                  marginLeft: 4,
+                  width: 24,
+                  height: 24,
+                  background: '#FAFCFF',
+                  borderRadius: 4,
+                  border: '1px solid #DAE2F2',
+                  display: 'flex',
+                  justifyContent: 'center',
+                  alignItems: 'center',
+                  cursor: 'pointer',
+                }}
+                onClick={() => {
+                  !isDisabled && updateTableHandle(editVal, record, title.code);
+                  setDisabledState((prevState: Record<string, boolean>) => ({
+                    ...prevState,
+                    [`${title.code}_${record.id}`]: !isDisabled, // 切换状态
+                  }));
+                }}
+              >
+                <IconFont style={{ fontSize: 16 }} type={isDisabled ? 'iconbianji' : 'iconqueren'} />
+              </div>
+
+              {history.length > 0 && (
+                <Popover className="qualificationAuth-popover" overlayInnerStyle={{ borderRadius: 4 }} content={content} title={false}>
+                  <IconFont type={'icon-qingliangtishi'} style={{ marginLeft: 10 }} />
+                </Popover>
+              )}
+            </div>
+          ) : (
+            <span>{title.type == 2 ? (typeof val == 'number' ? val : '-') : val}</span>
+          )}
+
+          {itemDicStr && itemDicStr.length > 0 && (
+            <Popover className="qualificationAuth-popover" overlayInnerStyle={{ borderRadius: 4 }} content={itemDicStr} title={false}>
+              <IconFont type={'icon-qingliangtishi'} style={{ marginLeft: 10 }} />
+            </Popover>
+          )}
+          {/* {
+          title.type == 2 && (
+            <Tooltip title="prompt text">
+              <IconFont type={'iconfuzhi'} style={{ marginLeft: 10 }} />
+            </Tooltip>
+          )
+        } */}
+        </>
+      ) : (
+        <div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center' }}>
+          {!isDisabled && (
+            <ProFormDigit
+              noStyle
+              width={80}
+              fieldProps={{
+                defaultValue: val && val,
+                onChange(value) {
+                  editVal = value;
+                },
+              }}
+            />
+          )}
+          {record[`${title.code}_pending`] && (
+            <div
+              style={{
+                marginLeft: 4,
+                width: 24,
+                height: 24,
+                background: '#FAFCFF',
+                borderRadius: 4,
+                border: '1px solid #DAE2F2',
+                display: 'flex',
+                justifyContent: 'center',
+                alignItems: 'center',
+                cursor: 'pointer',
+              }}
+              onClick={() => {
+                if (!isDisabled) {
+                  Modal.confirm({
+                    title: '修改确认',
+                    content: '确认后不可修改,是否保存?',
+                    okText: '确定',
+                    cancelText: '取消',
+                    onOk(...args) {
+                      updateTableHandle(editVal, record, title.code);
+                    },
+                    onCancel(...args) {},
+                  });
+                }
+                setDisabledState((prevState: Record<string, boolean>) => ({
+                  ...prevState,
+                  [`${title.code}_${record.id}`]: !isDisabled, // 切换状态
+                }));
+              }}
+            >
+              <IconFont style={{ fontSize: 16 }} type={isDisabled ? 'iconbianji' : 'iconqueren'} />
+            </div>
+          )}
+          {!record[`${title.code}_pending`] && <span>{typeof val == 'object' ? val.props.text : val}</span>}
+          {itemDicStr && itemDicStr.length > 0 && (
+            <Popover className="qualificationAuth-popover" overlayInnerStyle={{ borderRadius: 4 }} content={itemDicStr} title={false}>
+              <IconFont type={'icon-qingliangtishi'} style={{ marginLeft: 10 }} />
+            </Popover>
+          )}
+          {history.length > 0 && (
+            <Popover className="qualificationAuth-popover" overlayInnerStyle={{ borderRadius: 4 }} content={content} title={false}>
+              <IconFont type={'icon-qingliangtishi'} style={{ marginLeft: 10 }} />
+            </Popover>
+          )}
+        </div>
+      );
+    },
+  }));
+
+  const dataSource = itemDatas.map((row, rowIndex) => {
+    // 用 Record<string, any> 类型来解决动态属性问题
+    const rowData: Record<string, any> = { id: `row_${rowIndex}` };
+
+    row.forEach((item) => {
+      // 处理每个对象的 `value`
+      rowData[`${item.code}`] = item.value;
+
+      // 遍历当前 item 的所有属性
+      Object.keys(item).forEach((key) => {
+        if (key !== 'code' && key !== 'value') {
+          // 排除 code 和 value
+          rowData[`${item.code}_${key}`] = item[key]; // 将其他属性添加到 rowData 中
+        }
+      });
+    });
+
+    return rowData;
+  });
+
+  return { columns, dataSource };
+}

+ 211 - 0
src/pages/platform/setting/dataFilling/mineFilling/index.tsx

@@ -0,0 +1,211 @@
+import { createFromIconfontCN } from '@ant-design/icons';
+
+import { ActionType, ProColumns } from '@ant-design/pro-table';
+import { Input, Tabs } from 'antd';
+import { useEffect, useRef, useState } from 'react';
+import 'moment/locale/zh-cn';
+import { applyBatchItemValueReq, copyPrePeriodValueReq, generateTableDataReq } from './service';
+import './style.less';
+import { useModel } from 'umi';
+import KCTable from '@/components/kcTable';
+import generateTableData from './generateTableData';
+import { DATAFILL_PERIODTYPE } from '../fillingMana';
+
+const IconFont = createFromIconfontCN({
+  scriptUrl: '../../../../../../public/zhongtaiC.js',
+});
+
+type Data = {
+  [key: number]: string;
+};
+
+type LabelValue = {
+  label: string;
+  value: number;
+};
+
+const defaultYear = new Date().getFullYear();
+let total_tableDataSource: any[] = [];
+
+export default function MineFilling() {
+  const { initialState, setInitialState } = useModel('@@initialState');
+  const [tableDataFilterParams, set_tableDataFilterParams] = useState<any | undefined>();
+  const [tableDataSearchKeywords, set_tableDataSearchKeywords] = useState<string>('');
+
+  const detailTableRef = useRef<ActionType>();
+
+  const [tableDataSource, set_tableDataSource] = useState<any[]>([]);
+  const [tableColumns, set_tableColumns] = useState<ProColumns[]>([]);
+
+  const [reload, set_reload] = useState(false);
+  const [currentYear, set_currentYear] = useState(defaultYear);
+
+  const [currentTab, set_currentTab] = useState('1');
+  const [disabledState, setDisabledState] = useState<Record<string, boolean>>({});
+  const [selectedRows, set_selectedRows] = useState<any[]>([]);
+
+  // 新增:存储本地数据
+  const [localData, setLocalData] = useState<{ itemTitles: any[]; itemDatas: any[] }>({ itemTitles: [], itemDatas: [] });
+
+  // 修改:独立的数据生成函数,可以从本地数据生成表格数据
+  const generateTableDataFromLocal = () => {
+    const { itemTitles, itemDatas } = localData;
+    const { columns, dataSource } = generateTableData(itemTitles, itemDatas, { disabledState, setDisabledState, updateTableHandle, currentTab });
+    total_tableDataSource = dataSource;
+
+    set_tableColumns(columns);
+    set_tableDataSource(dataSource);
+  };
+
+  // 修改:获取数据的函数,只负责请求API,不直接依赖generateTableData
+  const getGenerateTableData = async () => {
+    const resp = await generateTableDataReq({ periodType: currentTab, dataYear: currentYear });
+    if (resp) {
+      const { itemTitles = [], itemDatas = [] } = resp;
+      // 存储数据到本地,避免每次都依赖传入参数
+      setLocalData({ itemTitles, itemDatas });
+      set_reload(false);
+    }
+  };
+
+  const updateTableHandle = async (updateData: any, record: any, key: string) => {
+    const unEditableColum = tableColumns.filter((a: any) => {
+      return !a.editable;
+    });
+
+    const result = {
+      fillItemCode: record[`${unEditableColum[0].dataIndex}_fillItemCode`],
+      dimensionCode: record[`${unEditableColum[0].dataIndex}_dimensionCode`],
+      periodCode: key,
+      value: updateData,
+      dataYear: currentYear,
+    };
+    const resp = await applyBatchItemValueReq([result]);
+    if (resp) {
+      set_reload(true);
+    }
+  };
+
+  const tableDataSearchHandle = (paramName: string) => {
+    const result = total_tableDataSource.filter((a: any) => a[`${paramName}`].indexOf(tableDataSearchKeywords) != -1);
+    set_tableDataSource(result);
+  };
+
+  const tabChangeHandle = (key: string) => {
+    // Tab change logic
+    set_currentTab(key);
+  };
+
+  const copyPrePeriodValueHandle = async () => {
+    const result = selectedRows.map((a) => {
+      let temp = Object.keys(a)
+        .filter((key) => /dimensionCode|fillItemCode/.test(key)) // 只保留包含 "dimensionCode" 或 "fillItemCode" 的键
+        .reduce((acc: any, key) => {
+          const newKey = key.split('_')[1]; // 移除前缀
+          acc[newKey] = a[key]; // 将数据按新键保存
+          return acc;
+        }, {});
+
+      return {
+        fillItemCode: temp.fillItemCode,
+        dimensionCode: temp.dimensionCode,
+      };
+    });
+
+    const resp = await copyPrePeriodValueReq(result);
+    if (resp) {
+      set_selectedRows([]);
+      set_reload(true);
+    }
+    set_selectedRows([]);
+  };
+
+  useEffect(() => {
+    getGenerateTableData();
+  }, [currentYear, currentTab]);
+
+  useEffect(() => {
+    reload && getGenerateTableData();
+  }, [reload]);
+
+  useEffect(() => {
+    generateTableDataFromLocal();
+  }, [disabledState, localData]);
+
+  return (
+    <div className="MineFilling">
+      <Tabs defaultActiveKey={currentTab} onChange={tabChangeHandle} items={DATAFILL_PERIODTYPE.map((a) => ({ label: a.label, key: `${a.value}` }))} />
+      <div className="MineFilling-detail-content">
+        <div className="toolBar">
+          <div className="filter">
+            <div className="filterItem">
+              <span className="label" style={{ whiteSpace: 'nowrap' }}>
+                {' '}
+                检索:
+              </span>
+              <Input
+                placeholder={'填报科室'}
+                allowClear
+                autoComplete="off"
+                suffix={<IconFont type="iconsousuo" style={{ color: '#99A6BF' }} onClick={() => tableDataSearchHandle('fillItemName')} />}
+                onChange={(e) => {
+                  set_tableDataSearchKeywords(e.target.value);
+                  if (e.target.value.length == 0) {
+                    set_tableDataSource(total_tableDataSource);
+                  }
+                }}
+                onPressEnter={(e) => {
+                  const result = total_tableDataSource.filter((a: any) => a[`fillItemName`].indexOf(tableDataSearchKeywords) != -1);
+                  set_tableDataSource(result);
+                }}
+              />
+            </div>
+          </div>
+          <div className="btnGroup">
+            {(currentTab == '1' || currentTab == '2') && (
+              <div className="changeBtn">
+                <div className="actBtn" onClick={() => set_currentYear(currentYear - 1)}>
+                  <IconFont type={'iconshuangzuo'} />
+                </div>
+                <span style={{ padding: '0 8px' }}>{currentYear}</span>
+                <div className="actBtn" onClick={() => set_currentYear(currentYear + 1)}>
+                  <IconFont type={'iconshuangyou'} />
+                </div>
+              </div>
+            )}
+          </div>
+        </div>
+        <KCTable
+          newVer
+          actionRef={detailTableRef}
+          columns={[...tableColumns]}
+          scroll={{ y: 'calc(100vh - 232px)' }}
+          rowKey="id"
+          dataSource={tableDataSource}
+          pagination={false}
+          tableAlertRender={false}
+          rowSelection={{
+            onChange(selectedRowKeys, selectedRows, info) {
+              set_selectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {selectedRows.length > 0 && (
+        <div className="bottom">
+          <div className="info">
+            已选择 <span>{selectedRows.length}</span>项,将上期数据带入至本期
+          </div>
+          <div className="btnGroup">
+            <a className="cancel" onClick={() => set_selectedRows([])}>
+              取消选择
+            </a>
+            <a className="confirm" onClick={() => copyPrePeriodValueHandle()}>
+              带入上期
+            </a>
+          </div>
+        </div>
+      )}
+    </div>
+  );
+}

+ 34 - 0
src/pages/platform/setting/dataFilling/mineFilling/service.ts

@@ -0,0 +1,34 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2023-03-03 16:31:27
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-02-21 11:35:02
+ * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/service.ts
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+
+import { request } from 'umi';
+
+//获取动态表格数据
+export const generateTableDataReq = (params: { periodType: string; dataYear: number; name?: string }) => {
+  return request('/centerSys/datafill/getMyItemDetail', {
+    method: 'GET',
+    params: { ...params },
+  });
+};
+
+//批量保存填报详情
+export const applyBatchItemValueReq = (data: any) => {
+  return request(`/centerSys/datafill/applyBatchItemValue`, {
+    method: 'POST',
+    data,
+  });
+};
+
+//带入上一期数据
+export const copyPrePeriodValueReq = (data: any) => {
+  return request(`/centerSys/datafill/copyPrePeriodValue`, {
+    method: 'POST',
+    data,
+  });
+};

+ 244 - 0
src/pages/platform/setting/dataFilling/mineFilling/style.less

@@ -0,0 +1,244 @@
+.kcmpPageContainer {
+  position: relative;
+  margin: 0 !important;
+  padding: 16px;
+}
+
+.MineFilling {
+  padding: 16px;
+  background: #ffffff;
+  border-radius: 4px;
+  padding-top: 0;
+
+  .kcmp-ant-tabs {
+    .kcmp-ant-tabs-nav {
+      &::before {
+        border-bottom: none;
+      }
+
+      .kcmp-ant-tabs-nav-wrap {
+        .kcmp-ant-tabs-nav-list {
+          .kcmp-ant-tabs-tab {
+            padding-bottom: 5px;
+          }
+        }
+      }
+    }
+  }
+
+  .toolBar {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 16px;
+
+    .filter {
+      display: flex;
+      flex-direction: row;
+      justify-content: flex-start;
+      align-items: center;
+
+      .filterItem {
+        display: flex;
+        flex-direction: row;
+        justify-content: center;
+        align-items: center;
+      }
+    }
+
+    .btnGroup {
+      .import,
+      .export {
+        cursor: pointer;
+        display: inline-block;
+        font-size: 14px;
+        font-weight: 400;
+        color: #17181a;
+        line-height: 24px;
+        padding: 0 14px;
+        margin-right: 8px;
+        background: #fafcff;
+        border-radius: 4px;
+        border: 1px solid #dae2f2;
+      }
+
+      .add {
+        cursor: pointer;
+        display: inline-block;
+        font-size: 14px;
+        font-weight: 400;
+        color: #ffffff;
+        line-height: 24px;
+        padding: 0 14px;
+        background: #3377ff;
+        border-radius: 4px;
+      }
+
+      .changeBtn {
+        display: flex;
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: center;
+
+        .actBtn {
+          cursor: pointer;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          width: 24px;
+          height: 24px;
+          background: #fafcff;
+          border-radius: 4px;
+          border: 1px solid #dae2f2;
+        }
+      }
+    }
+  }
+
+  .FillingMana-detail {
+    .FillingMana-detail-header {
+      height: 80px;
+      padding: 16px;
+      background: #ffffff;
+      border-radius: 4px;
+      margin-bottom: 16px;
+
+      .FillingMana-detail-header-title {
+        display: flex;
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: center;
+        margin-bottom: 10px;
+
+        .backBtn {
+          cursor: pointer;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          width: 24px;
+          height: 24px;
+          background: #fafcff;
+          border-radius: 4px;
+          margin-right: 12px;
+          border: 1px solid #dae2f2;
+        }
+
+        & > span {
+          font-weight: bold;
+          font-size: 20px;
+          color: #17181a;
+          height: 20px;
+          line-height: 20px;
+        }
+      }
+
+      .FillingMana-detail-header-title-sub {
+        display: flex;
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: center;
+        padding-left: 36px;
+
+        & > span {
+          display: inline-block;
+          height: 14px;
+          line-height: 14px;
+          font-weight: 400;
+          font-size: 14px;
+          color: #525866;
+          margin-right: 24px;
+        }
+      }
+    }
+
+    .FillingMana-detail-content {
+      padding: 16px;
+      background: #ffffff;
+      border-radius: 4px;
+
+      .toolBar {
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 16px;
+
+        .filter {
+          display: flex;
+          flex-direction: row;
+          justify-content: flex-start;
+          align-items: center;
+
+          .filterItem {
+            display: flex;
+            flex-direction: row;
+            justify-content: center;
+            align-items: center;
+          }
+        }
+
+        .btnGroup {
+        }
+      }
+    }
+  }
+
+  .bottom {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    align-items: center;
+    width: 100%;
+    position: absolute;
+    left: 0;
+    bottom: 0;
+    height: 48px;
+    padding: 0 16px;
+    background: #ffffff;
+    box-shadow: 0px -8px 16px 0px rgba(64, 85, 128, 0.1);
+
+    .info {
+      font-weight: 400;
+      font-size: 14px;
+      color: #17181a;
+
+      & > span {
+        color: #3376fe;
+      }
+    }
+
+    .btnGroup {
+      display: flex;
+      flex-direction: row;
+      justify-content: flex-start;
+      align-items: center;
+
+      .cancel {
+        display: inline-block;
+        width: 80px;
+        height: 24px;
+        text-align: center;
+        line-height: 24px;
+        font-size: 14px;
+        color: #17181a;
+        background: #fafcff;
+        border-radius: 4px;
+        margin-right: 8px;
+        border: 1px solid #dae2f2;
+      }
+      .confirm {
+        display: inline-block;
+        width: 80px;
+        height: 24px;
+        text-align: center;
+        line-height: 24px;
+        font-size: 14px;
+        color: #ffffff;
+        background: #3377ff;
+        border-radius: 4px;
+        margin-right: 8px;
+      }
+    }
+  }
+}

+ 320 - 297
src/pages/platform/setting/departmentMana/index.tsx

@@ -2,331 +2,354 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2023-03-03 11:30:33
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-12-02 14:02:58
+ * @LastEditTime: 2025-01-14 10:30:57
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/index.tsx
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
 
-
-
 import { KCInput } from '@/components/KCInput';
 import KCTable from '@/components/kcTable';
 import KCUpload from '@/components/KCUpload';
 import { getAllHosp } from '@/service/hospList';
 import { downloadTemplateReq } from '@/utils';
-import { ModalForm, ProFormSelect, ProFormText, ProFormTextArea } from '@ant-design/pro-form'
+import { ModalForm, ProFormSelect, ProFormText, ProFormTextArea } from '@ant-design/pro-form';
 import { ProColumns } from '@ant-design/pro-table';
 import { message, Modal, Popconfirm } from 'antd';
 import FormItem from 'antd/lib/form/FormItem';
-import { useEffect, useState } from 'react'
+import { useEffect, useState } from 'react';
 import { addData, delData, editData, getDepartmentData, getDepartmentType, getRelaHosp, importDepartmentData } from './service';
 
 import './style.less';
-
-
+import { useModel } from 'umi';
+import KCProSelect from '@/components/KCProSelect';
+import { getDictByDictTypeAndSysid } from '@/service/dictionary';
+import { KcimCenterSysId } from '@/constant';
+import { checkRoleTagsIsObjArr } from '../roleManage/modals/modal';
 
 export default function DepartmentMana() {
-
-    const [tableDataFilterParams, set_tableDataFilterParams] = useState<any | undefined>();
-    const [tableDataSearchKeywords, set_tableDataSearchKeywords] = useState<string>('');
-    const [reload, set_reload] = useState(false);
-    const [currentEdit, set_currentEdit] = useState<any>(undefined);
-
-    const [types, set_types] = useState([]);
-
-
-
-
-    const columns = [
-        {
-            title: '科室代码',
-            dataIndex: 'code',
-        },
-        {
-            title: '科室名称',
-            dataIndex: 'name',
-        },
-        {
-            title: '院区',
-            dataIndex: 'hospName',
-
-        },
-        {
-            title: '类型',
-            dataIndex: 'type',
-            valueType: 'select',
-            fieldProps: {
-                options: types
-            }
-        },
-        {
-            title: '备注',
-            dataIndex: 'remark',
-
-        },
-        {
-            title: '操作',
-            key: 'option',
-            width: 120,
-            valueType: 'option',
-            render: (_: any, record: any) => {
-                return [
-                    <UpDataActBtn key={'act'} record={record} type='EDIT' />,
-                    <Popconfirm
-                        title="是否确认删除?"
-                        key="del"
-                        onConfirm={() => delTableData(record)}
-                    >
-                        <a>删除</a>
-                    </Popconfirm>
-                ]
-            },
-        },
-
-    ]
-
-
-    const getTableData = async (params: any) => {
-        const resp = await getDepartmentData(params);
-        set_reload(false);
-        if (resp) {
-            return {
-                data: resp.list,
-                success: true,
-                total: resp.totalCount,
-                pageSize: resp.pageSize,
-                totalPage: resp.totalPage,
-            }
-        }
-        return []
+  const { initialState, setInitialState } = useModel('@@initialState');
+  const [tableDataFilterParams, set_tableDataFilterParams] = useState<any | undefined>();
+  const [tableDataSearchKeywords, set_tableDataSearchKeywords] = useState<string>('');
+  const [reload, set_reload] = useState(false);
+  const [currentEdit, set_currentEdit] = useState<any>(undefined);
+
+  const [types, set_types] = useState([]);
+
+  const columns = [
+    {
+      title: initialState?.customerType == '2' ? '组织编码' : '科室代码',
+      dataIndex: 'code',
+    },
+    {
+      title: initialState?.customerType == '2' ? '组织名称' : '科室名称',
+      dataIndex: 'name',
+    },
+    {
+      title: initialState?.customerType == '2' ? '园区' : '院区',
+      dataIndex: 'hospName',
+    },
+    {
+      title: initialState?.customerType == '2' ? '组织类型' : '科室类型',
+      dataIndex: 'type',
+      valueType: 'select',
+      fieldProps: {
+        options: types,
+      },
+    },
+    {
+      title: initialState?.customerType == '2' ? '组织标签' : '科室标签',
+      renderText(text: any, record: any) {
+        const { tagList = [] } = record;
+        return (
+          <>
+            {tagList.map((a: any, i: number) => (
+              <span
+                style={{
+                  display: 'inline-flex',
+                  padding: '2px 5px',
+                  fontSize: 12,
+                  background: '#EEF3FA',
+                  borderRadius: 4,
+                  color: '#525866',
+                  marginRight: 4,
+                }}
+              >
+                {a.tagName}
+              </span>
+            ))}
+          </>
+        );
+      },
+    },
+    {
+      title: '备注',
+      dataIndex: 'remark',
+    },
+    {
+      title: '操作',
+      key: 'option',
+      width: 120,
+      valueType: 'option',
+      render: (_: any, record: any) => {
+        return [
+          <UpDataActBtn key={'act'} record={record} type="EDIT" />,
+          <Popconfirm title="是否确认删除?" key="del" onConfirm={() => delTableData(record)}>
+            <a>删除</a>
+          </Popconfirm>,
+        ];
+      },
+    },
+  ];
+
+  const getTableData: any = async (params: any) => {
+    const resp = await getDepartmentData(params);
+    set_reload(false);
+    if (resp) {
+      return {
+        data: resp.list,
+        success: true,
+        total: resp.totalCount,
+        pageSize: resp.pageSize,
+        totalPage: resp.totalPage,
+      };
     }
-
-    const delTableData = async (record: any) => {
-        const resp = await delData(record.id);
-        if (resp) {
-            set_reload(true);
-            // message.success('操作成功!');
-        }
+    return [];
+  };
+
+  const delTableData = async (record: any) => {
+    const resp = await delData(record.id);
+    if (resp) {
+      set_reload(true);
+      // message.success('操作成功!');
+    }
+  };
+
+  const updateTable = async (formVal: any, type: 'EDIT' | 'ADD') => {
+    if (type == 'ADD') {
+      const resp = await addData({ ...formVal, tagList: formVal.tagList.map((a: any) => ({ tagCode: a.value, tagName: a.label })) });
+      if (resp) {
+        set_reload(true);
+      }
     }
 
-    const updateTable = async (formVal: any, type: 'EDIT' | "ADD") => {
+    if (type == 'EDIT') {
+      const resp = await editData({
+        ...formVal,
+        tagList: checkRoleTagsIsObjArr(formVal.tagList) ? formVal.tagList.map((a: any) => ({ tagCode: a.value, tagName: a.label })) : currentEdit?.tagList,
+      });
+      if (resp) {
+        set_reload(true);
+      }
+    }
+  };
 
-        if (type == 'ADD') {
-            const resp = await addData(formVal);
-            if (resp) {
-                set_reload(true);
-            }
+  const UpDataActBtn = ({ record, type }: { record: any; type: 'EDIT' | 'ADD' }) => {
+    if (type == 'EDIT') set_currentEdit(record);
+    return (
+      <ModalForm
+        title={`${type == 'EDIT' ? '编辑' : '新增'}科室`}
+        width={352}
+        initialValues={type == 'EDIT' ? { ...record, tagList: record?.tagList?.map((a: any) => a.tagCode) ?? [] } : {}}
+        trigger={
+          type == 'EDIT' ? (
+            <a key="edit">编辑</a>
+          ) : (
+            <span className="add" style={{ background: '#3377FF', color: '#fff' }}>
+              新增
+            </span>
+          )
         }
+        onFinish={(val) => {
+          return updateTable(type == 'EDIT' ? { ...val, id: record.id } : val, type);
+        }}
+      >
+        <ProFormText
+          name="name"
+          label={initialState?.customerType == '2' ? '组织名称:' : '科室名称:'}
+          placeholder="请输入"
+          rules={[{ required: true, message: initialState?.customerType == '2' ? '组织名称不能为空!' : '科室名称不能为空!' }]}
+        />
+        <ProFormText
+          name="code"
+          label={initialState?.customerType == '2' ? '组织编码:' : '科室代码:'}
+          placeholder="请输入"
+          rules={[{ required: true, message: initialState?.customerType == '2' ? '组织编码不能为空!' : '科室代码不能为空!' }]}
+        />
+        <ProFormSelect
+          name="type"
+          label={initialState?.customerType == '2' ? '组织类型:' : '科室类型:'}
+          placeholder="请选择类型"
+          fieldProps={{ size: 'small' }}
+          rules={[{ required: true, message: '类型不能为空!' }]}
+          request={async () => {
+            return types;
+          }}
+        />
+        <ProFormSelect
+          name="hospId"
+          label={initialState?.customerType == '2' ? '园区' : '院区:'}
+          fieldProps={{ size: 'small' }}
+          placeholder="请选择"
+          rules={[{ required: true, message: initialState?.customerType == '2' ? '园区能为空!' : '院区不能为空!' }]}
+          request={async () => {
+            const resp = await getRelaHosp();
+            if (resp) {
+              const data: any = resp?.map((a: any) => ({
+                label: a.name,
+                value: a.id,
+              }));
 
-        if (type == 'EDIT') {
-            const resp = await editData({ ...formVal });
+              return data;
+            }
+            return [];
+          }}
+        />
+        <KCProSelect
+          label={initialState?.customerType == '2' ? '组织标签:' : '科室标签:'}
+          width="md"
+          name="tagList"
+          fieldProps={{
+            size: 'small',
+            labelInValue: true,
+            mode: 'tags',
+          }}
+          request={async () => {
+            const resp = await getDictByDictTypeAndSysid(KcimCenterSysId, 'ORGANIZATION_LABEL_DIC');
             if (resp) {
-                set_reload(true);
+              return resp.dataVoList.map((t: any) => ({
+                label: t.name,
+                value: t.code,
+              }));
             }
-        }
-
-
+            return [];
+          }}
+        />
+        <ProFormTextArea name="remark" label="说明:" placeholder="请输入" />
+      </ModalForm>
+    );
+  };
+
+  const tableDataSearchHandle = (paramName: string) => {
+    set_tableDataFilterParams({
+      ...tableDataFilterParams,
+      current: 1,
+      [`${paramName}`]: tableDataSearchKeywords,
+    });
+  };
+
+  const downloadTemplateFileHandle = async () => {
+    await downloadTemplateReq('/centerSys/sysdepartment/export');
+  };
+
+  const importData = (name: string) => {
+    return (
+      <ModalForm
+        title={`导入${name}数据`}
+        width={352}
+        trigger={<span key="3">导入</span>}
+        onFinish={async (values) => {
+          const {
+            importFile: { fileList },
+          } = values;
+
+          let formData = new FormData();
+          formData.append('file', fileList[0].originFileObj);
+          const resp = await importDepartmentData(formData);
+
+          if (resp) {
+            set_reload(true);
+            return true;
+          }
+        }}
+      >
+        <FormItem name={'importFile'}>
+          <KCUpload downloadTemplateFile={() => downloadTemplateFileHandle()} />
+        </FormItem>
+      </ModalForm>
+    );
+  };
+
+  const getAllDepartmentType = async () => {
+    const resp = await getDepartmentType();
+    if (resp) {
+      const data: any = resp?.map((a) => ({
+        label: a.name,
+        value: a.code,
+      }));
+      set_types(data);
     }
-
-    const UpDataActBtn = ({ record, type }: { record: any, type: 'EDIT' | 'ADD' }) => {
-
-        return (
-            <ModalForm
-                title={`${type == 'EDIT' ? '编辑' : '新增'}科室`}
-                width={352}
-                initialValues={type == 'EDIT' ? { ...record } : {}}
-                trigger={
-                    type == 'EDIT' ? <a key="edit">编辑</a> : <span className='add' style={{background:'#3377FF',color:'#fff'}}>新增</span>
-                }
-                onFinish={(val) => {
-                    return updateTable(type == 'EDIT' ? { ...val, id: record.id } : val, type);
+  };
+
+  useEffect(() => {
+    getAllDepartmentType();
+  }, []);
+
+  return (
+    <div className="DepartmentMana">
+      <div className="toolBar">
+        <div className="filter">
+          <div className="filterItem" style={{ marginRight: 16 }}>
+            <span className="label">类型:</span>
+            {types.length > 0 && (
+              <ProFormSelect
+                name="type"
+                noStyle
+                style={{ width: 160 }}
+                placeholder="请选择类型"
+                rules={[{ required: true, message: '类型不能为空!' }]}
+                request={async () => {
+                  return types;
                 }}
-            >
-                <ProFormText
-                    name="name"
-                    label="科室名称:"
-                    placeholder="请输入"
-                    rules={[{ required: true, message: '科室名称不能为空!' }]}
-                />
-                <ProFormText
-                    name="code"
-                    label="科室代码:"
-                    placeholder="请输入"
-                    rules={[{ required: true, message: '科室代码不能为空!' }]}
-                />
-                <ProFormSelect
-                    name="type"
-                    label="类型:"
-                    placeholder="请选择类型"
-                    fieldProps={{size:'small'}}
-                    rules={[{ required: true, message: '类型不能为空!' }]}
-                    request={async () => {
-                        return types
-                    }}
-                />
-                <ProFormSelect
-                    name="hospId"
-                    label="院区:"
-                    fieldProps={{size:'small'}}
-                    placeholder="请选择院区"
-                    rules={[{ required: true, message: '院区不能为空!' }]}
-                    request={async () => {
-                        const resp = await getRelaHosp();
-                        if (resp) {
-                            const data: any = resp?.map((a:any) => ({
-                                label: a.name,
-                                value: a.id
-                            }));
-
-                            return data;
-                        }
-                        return []
-
-                    }}
-                />
-                <ProFormTextArea
-                    name="remark"
-                    label="说明:"
-                    placeholder="请输入"
-
-                />
-            </ModalForm>
-        )
-    }
-
-
-    const tableDataSearchHandle = (paramName: string) => {
-
-
-        set_tableDataFilterParams({
-            ...tableDataFilterParams,
-            current:1,
-            [`${paramName}`]: tableDataSearchKeywords
-        })
-    }
-
-
-    const downloadTemplateFileHandle =async () => {
-        await downloadTemplateReq('/centerSys/sysdepartment/export');
-    }
-
-
-    const importData = ( name: string) => {
-
-        return (
-            <ModalForm
-                title={`导入${name}数据`}
-                width={352}
-                trigger={
-                    <span  key="3">导入</span>
-                }
-                onFinish={async (values) => {
-                    
-                    const { importFile: { fileList } } = values;
-
-                    let formData = new FormData();
-                    formData.append('file', fileList[0].originFileObj);
-                    const resp = await importDepartmentData(formData);
-
-                    if (resp) {
-                        set_reload(true);
-                        return true;
+                fieldProps={{
+                  size: 'small',
+                  onChange: (e) => {
+                    if (e) {
+                      set_tableDataFilterParams({
+                        ...tableDataFilterParams,
+                        current: 1,
+                        departType: e,
+                      });
+                    } else {
+                      set_tableDataFilterParams({
+                        ...tableDataFilterParams,
+                        current: 1,
+                        departType: '',
+                      });
                     }
-
+                  },
                 }}
-            >
-                <FormItem name={'importFile'}>
-                    <KCUpload downloadTemplateFile={()=>downloadTemplateFileHandle()} />
-                </FormItem>
-
-            </ModalForm>
-        )
-    }
-
-
-    const getAllDepartmentType = async () => {
-        const resp = await getDepartmentType();
-        if (resp) {
-            const data: any = resp?.map((a) => ({
-                label: a.name,
-                value: a.code
-            }));
-            set_types(data);
-        }
-    }
-
-
-    useEffect(() => {
-        getAllDepartmentType();
-    }, [])
-
-    return (
-        <div className='DepartmentMana'>
-            <div className='toolBar'>
-                <div className='filter'>
-                    <div className='filterItem' style={{ marginRight: 16 }}>
-                        <span className='label'>类型:</span>
-                        {
-                            types.length > 0 && (
-                                <ProFormSelect
-                                    name="type"
-                                    noStyle
-                                    style={{ width: 160 }}
-                                    placeholder="请选择类型"
-                                    rules={[{ required: true, message: '类型不能为空!' }]}
-                                    request={async () => {
-                                        return types
-                                    }}
-                                    fieldProps={{
-                                        size:'small',
-                                        onChange: (e) => {
-
-                                            if (e) {
-                                                set_tableDataFilterParams({
-                                                    ...tableDataFilterParams,
-                                                    current:1,
-                                                    departType: e
-                                                });
-                                            } else {
-                                                set_tableDataFilterParams({
-                                                    ...tableDataFilterParams,
-                                                    current:1,
-                                                    departType: ''
-                                                });
-                                            }
-
-                                        }
-                                    }}
-                                />
-                            )
-                        }
-                    </div>
-                    <div className='filterItem' >
-                        <span className='label'>检索:</span>
-                        <KCInput placeholder={'请输入科室名称'} style={{ width: 160 }} search allowClear
-                            onChange={(e) => {
-                                set_tableDataSearchKeywords(e.target.value);
-                                if (e.target.value.length == 0) {
-                                    set_tableDataFilterParams({
-                                        ...tableDataFilterParams,
-                                        current:1,
-                                        departName: ''
-                                    });
-                                }
-                            }}
-                            onSearch={() => tableDataSearchHandle('departName')}
-
-                        />
-                    </div>
-                </div>
-                <div className='btnGroup'>
-                    {importData('科室')}
-                    <UpDataActBtn record type='ADD' />
-                </div>
-            </div>
-            <div style={{ marginTop: 16 }}>
-                <KCTable columns={columns as ProColumns[]} scroll={{y:`calc(100vh - 255px)`}} reload={reload} rowKey='id' newVer params={tableDataFilterParams} request={(params) => getTableData(params)} />
-            </div>
+              />
+            )}
+          </div>
+          <div className="filterItem">
+            <span className="label">检索:</span>
+            <KCInput
+              placeholder={initialState?.customerType == '2' ? '请输入组织名称!' : '请输入科室名称'}
+              style={{ width: 160 }}
+              search
+              allowClear
+              onChange={(e) => {
+                set_tableDataSearchKeywords(e.target.value);
+                if (e.target.value.length == 0) {
+                  set_tableDataFilterParams({
+                    ...tableDataFilterParams,
+                    current: 1,
+                    departName: '',
+                  });
+                }
+              }}
+              onSearch={() => tableDataSearchHandle('departName')}
+            />
+          </div>
+        </div>
+        <div className="btnGroup">
+          {importData(initialState?.customerType == '2' ? '组织' : '科室')}
+          <UpDataActBtn record type="ADD" />
         </div>
-    )
+      </div>
+      <div style={{ marginTop: 16 }}>
+        <KCTable columns={columns as ProColumns[]} scroll={{ y: `calc(100vh - 255px)` }} reload={reload} rowKey="id" newVer params={tableDataFilterParams} request={(params) => getTableData(params)} />
+      </div>
+    </div>
+  );
 }

+ 105 - 0
src/pages/platform/setting/embeddedDashboard/index.tsx

@@ -0,0 +1,105 @@
+import React, { useEffect } from 'react';
+import { embedDashboard } from '@superset-ui/embedded-sdk';
+import './style.less';
+
+function MyEmbeddedDashboard() {
+  // 从 URL 中获取仪表板 ID
+  const paths = window.location.pathname.split('/').filter((item) => item);
+  const dashboardId = paths[paths.length - 1];
+  console.log('paths', paths);
+  console.log('dashboardId:', dashboardId);
+
+  // 封装获取 Guest Token 的异步函数
+  const getGuestToken = async () => {
+    try {
+      // 1. 登录,获取 access_token
+      const loginResponse = await fetch('/api/v1/security/login', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({
+          username: 'admin',
+          password: 'admin',
+          provider: 'db', // 根据你的实际配置,比如 db 或者其他
+          refresh: true,
+        }),
+      });
+
+      const loginData = await loginResponse.json();
+      const accessToken = loginData?.access_token;
+      if (!accessToken) {
+        throw new Error('登录失败,未获取到 access_token');
+      }
+
+      // 2. 使用 access_token 调用 /api/v1/security/guest_token/ 获取 Guest Token
+      const guestTokenResponse = await fetch('/api/v1/security/guest_token/', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+          // 将 access_token 作为 Bearer Token
+          Authorization: `Bearer ${accessToken}`,
+        },
+        body: JSON.stringify({
+          user: {
+            username: 'guest_user',
+            first_name: 'Guest',
+            last_name: 'User',
+            email: 'guest@example.com',
+          },
+          resources: [
+            {
+              type: 'dashboard',
+              id: dashboardId, // 使用从地址栏获取到的 dashboardId
+            },
+          ],
+          rls: [],
+        }),
+      });
+
+      const guestTokenData = await guestTokenResponse.json();
+      const guestToken = guestTokenData?.token;
+      if (!guestToken) {
+        throw new Error('获取 Guest Token 失败');
+      }
+
+      // 返回最终需要的 Guest Token
+      return guestToken;
+    } catch (error) {
+      console.error(error);
+      // 如果获取失败,可根据需求返回一个空字符串或抛出异常
+      return '';
+    }
+  };
+
+  useEffect(() => {
+    // 当 dashboardId 存在时,嵌入仪表板
+    if (dashboardId) {
+      embedDashboard({
+        id: dashboardId,
+        supersetDomain: 'http://localhost:8088', // 你的 Superset 服务地址
+        mountPoint: document.getElementById('dashboard-container') as HTMLElement,
+        dashboardUiConfig: {
+          hideTitle: true,
+          filters: {
+            expanded: true,
+          },
+        },
+        // 调用上面封装的 getGuestToken 函数
+        fetchGuestToken: async () => {
+          const token = await getGuestToken();
+          return token;
+        },
+        iframeSandboxExtras: ['allow-top-navigation', 'allow-popups-to-escape-sandbox'],
+      });
+    }
+  }, [dashboardId]);
+
+  return (
+    <div className="my-embedded-dashboard">
+      <div className="dashboard" id="dashboard-container" style={{ width: '100%', height: '80vh' }} />
+    </div>
+  );
+}
+
+export default MyEmbeddedDashboard;

+ 0 - 0
src/pages/platform/setting/embeddedDashboard/service.ts


+ 9 - 0
src/pages/platform/setting/embeddedDashboard/style.less

@@ -0,0 +1,9 @@
+.my-embedded-dashboard {
+  .dashboard {
+    & > iframe {
+      width: 100%;
+      height: 100%;
+      border: none;
+    }
+  }
+}

+ 35 - 26
src/pages/platform/setting/hospManage/index.tsx

@@ -1,14 +1,14 @@
 /*
  * @Author: your name
  * @Date: 2022-01-13 15:22:48
- * @LastEditTime: 2024-12-02 14:01:28
+ * @LastEditTime: 2025-02-18 15:09:49
  * @LastEditors: code4eat awesomedema@gmail.com
  * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/hospManage/index.tsx
  */
 
 import { FC, Key, useEffect, useMemo, useState } from 'react';
-import { hospManageModelState, ConnectProps, Loading, connect } from 'umi';
+import { hospManageModelState, ConnectProps, Loading, connect, useModel } from 'umi';
 import { Button, Checkbox, Divider, Drawer, Dropdown, Input, Popconfirm, Switch, Table, Tree, TreeProps, message } from 'antd';
 import KCTable from '@/components/kcTable';
 import type { ProColumns } from '@ant-design/pro-table';
@@ -43,7 +43,7 @@ export enum TableActType {
 const { DirectoryTree } = Tree;
 
 const SearchIcon = createFromIconfontCN({
-  scriptUrl: '//at.alicdn.com/t/c/font_1927152_g1njmm1kh7b.js',
+  scriptUrl: '',
 });
 
 interface PageProps extends ConnectProps {
@@ -83,12 +83,12 @@ export function extractAttributeValues(tree: TreeNode, attributeName: keyof Tree
       values = values.concat(extractAttributeValues(child, attributeName));
     }
   }
-  
 
   return values;
 }
 
 const DrawerActBtn = ({ record }: { record: any }) => {
+  const { initialState, setInitialState } = useModel('@@initialState');
   const [treeData, set_treeData] = useState<getTreeDataRespType[]>([]);
   const [currentSelectedTreeNode, set_currentSelectedTreeNode] = useState<any | undefined>(undefined);
   const [drawerTablereload, set_drawerTablereload] = useState(false);
@@ -326,7 +326,7 @@ const DrawerActBtn = ({ record }: { record: any }) => {
   };
 
   const getTableData = async (params: any, sort: any, filter: any) => {
-    console.log({params});
+    console.log({ params });
     set_drawerTablereload(false);
     if (currentSelectedTreeNode) {
       const resp = await getTableDataRequest({ ...params, systemId: currentSelectedTreeNode.code });
@@ -400,7 +400,7 @@ const DrawerActBtn = ({ record }: { record: any }) => {
   const drawerTableDataSearchHandle = (paramName: string) => {
     set_drawerTableDataFilterParams({
       ...drawerTableDataFilterParams,
-      current:1,
+      current: 1,
       [`${paramName}`]: drawerTableDataSearchKeywords,
     });
   };
@@ -485,7 +485,7 @@ const DrawerActBtn = ({ record }: { record: any }) => {
     >
       <div className="setApiPermDrawer">
         <div className="topbar">
-          <div className="title">{`院区菜单权限设置(${record.hospName})`}</div>
+          <div className="title">{initialState?.customerType == '2' ? `园区菜单权限设置(${record.hospName})` : `院区菜单权限设置(${record.hospName})`}</div>
           <div className="btnGroup">
             <span className={ifFrozze ? 'clearBtn disabled' : 'clearBtn'} onClick={() => clearFunction()}>
               清除功能权限
@@ -710,7 +710,7 @@ const DrawerActBtn = ({ record }: { record: any }) => {
 
 const HospManage: FC<PageProps> = ({ hospManageModel: state, dispatch }) => {
   const { reloadTable } = state;
-
+  const { initialState, setInitialState } = useModel('@@initialState');
   const [tableDataFilterParams, set_tableDataFilterParams] = useState<any | undefined>();
   const [tableDataSearchKeywords, set_tableDataSearchKeywords] = useState<string>('');
   const [tabIds, set_tabIds] = useState<Key[]>([]);
@@ -720,16 +720,16 @@ const HospManage: FC<PageProps> = ({ hospManageModel: state, dispatch }) => {
 
   const columns: ProColumns<TableListItem>[] = [
     {
-      title: '院区ID',
+      title: initialState?.customerType == '2' ? '园区ID' : '院区ID',
       dataIndex: 'id',
-      width:70
+      width: 70,
     },
     {
-      title: '院区名称',
+      title: initialState?.customerType == '2' ? '园区名称' : '院区名称',
       dataIndex: 'hospName',
       hideInSearch: false,
-      ellipsis:true,
-      width:200
+      ellipsis: true,
+      width: 200,
     },
     // {
     //   title: '是否主院',
@@ -739,34 +739,34 @@ const HospManage: FC<PageProps> = ({ hospManageModel: state, dispatch }) => {
     //   },
     // },
     {
-      title: '医院标识',
+      title: initialState?.customerType == '2' ? '园区标识' : '医院标识',
       dataIndex: 'hospSign',
-      ellipsis:true
+      ellipsis: true,
     },
     // {
     //   title: '简称',
     //   dataIndex: 'hospAbbreviation',
     // },
     {
-      title: '主院名称',
+      title: initialState?.customerType == '2' ? '主园区名称' : '主院名称',
       dataIndex: 'parentName',
-      ellipsis:true
+      ellipsis: true,
     },
     {
-      title: '医院等级',
+      title: initialState?.customerType == '2' ? '园区等级' : '医院等级',
       dataIndex: 'hospitalLevelName',
-      ellipsis:true,
-      width:90
+      ellipsis: true,
+      width: 90,
     },
     // {
     //   title: '医院类型',
     //   dataIndex: 'hospitalType',
     // },
     {
-      title: '医院性质',
+      title: initialState?.customerType == '2' ? '园区性质' : '医院性质',
       dataIndex: 'hospitalNatureName',
-      ellipsis:true,
-      width:90
+      ellipsis: true,
+      width: 90,
     },
     // {
     //   title: '医院编码',
@@ -775,7 +775,7 @@ const HospManage: FC<PageProps> = ({ hospManageModel: state, dispatch }) => {
     {
       title: '系统名称',
       dataIndex: 'systemName',
-      ellipsis:true
+      ellipsis: true,
     },
     // {
     //   title: '互通',
@@ -1004,7 +1004,7 @@ const HospManage: FC<PageProps> = ({ hospManageModel: state, dispatch }) => {
   const tableDataSearchHandle = (paramName: string) => {
     set_tableDataFilterParams({
       ...tableDataFilterParams,
-      current:1,
+      current: 1,
       [`${paramName}`]: tableDataSearchKeywords,
     });
   };
@@ -1049,7 +1049,16 @@ const HospManage: FC<PageProps> = ({ hospManageModel: state, dispatch }) => {
           </span>
         </div>
       </div>
-      <KCTable newVer rowKey="id" scroll={{y:`calc(100vh - 250px)`}} columns={columns} reload={reloadTable} options={false} params={tableDataFilterParams} request={(params) => getHospData(params)} />
+      <KCTable
+        newVer
+        rowKey="id"
+        scroll={{ y: `calc(100vh - 250px)` }}
+        columns={columns}
+        reload={reloadTable}
+        options={false}
+        params={tableDataFilterParams}
+        request={(params) => getHospData(params)}
+      />
     </div>
   );
 };

+ 15 - 14
src/pages/platform/setting/hospManage/modals/modal.tsx

@@ -1,7 +1,7 @@
 /*
  * @Author: your name
  * @Date: 2022-01-12 17:11:11
- * @LastEditTime: 2024-10-11 16:52:20
+ * @LastEditTime: 2024-12-09 15:54:33
  * @LastEditors: code4eat awesomedema@gmail.com
  * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/userManage/modal.tsx
@@ -10,7 +10,7 @@
 import React, { useEffect, useRef, useState } from 'react';
 import KCModal from '@/components/KCModal';
 import KCProSelect from '@/components/KCProSelect';
-import { hospManageModelState, Dispatch } from 'umi';
+import { hospManageModelState, Dispatch, useModel } from 'umi';
 
 import { ProFormText, ProFormRadio, ProFormDependency, ProFormDigit } from '@ant-design/pro-form';
 import type { ProColumns } from '@ant-design/pro-table';
@@ -30,6 +30,7 @@ interface ActModalProps extends hospManageModelState {
 type DataSourceType = YoushuAccountsType & { key: number };
 
 const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, currentEdit }) => {
+  const { initialState,setInitialState } = useModel('@@initialState');
   const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
   const [dataSource, setDataSource] = useState<DataSourceType[]>([]);
 
@@ -124,13 +125,13 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
 
   const setModalTitle = () => {
     if (tableAct == TableActType.EDIT) {
-      return '编辑院区';
+      return initialState?.customerType == '2'?'编辑园区':'编辑院区';
     }
     if (tableAct == TableActType.ADD) {
-      return '新增院区';
+      return initialState?.customerType == '2'?'新增园区':'新增院区';
     }
     if (tableAct == TableActType.BINDACCOUNT) {
-      return '院区报告设置';
+      return initialState?.customerType == '2'?'园区报告设置':'院区报告设置';
     }
   };
 
@@ -223,17 +224,17 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
           <ProFormText
             width="md"
             name="hospName"
-            label="医院名称"
+            label={initialState?.customerType == '2'?'园区名称':'医院名称'}
             placeholder="请输入"
             rules={[
               {
                 required: true,
-                message: '请输入医院名称!',
+                message: initialState?.customerType == '2'?'请输入园区名称!':'请输入医院名称!',
               },
             ]}
           />
           <KCProSelect
-            label="是否主院"
+            label={initialState?.customerType == '2'?'是否主园区':'是否主院'}
             width="md"
             name="isHospital"
             fieldProps={{size:'small'}}
@@ -259,7 +260,7 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
               if (isHospital && isHospital == 1) {
                 return (
                   <KCProSelect
-                    label="选择主院"
+                    label={initialState?.customerType == '2'?'选择主园区':'选择主院'}
                     width="md"
                     name="parentId"
                     fieldProps={{
@@ -279,7 +280,7 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
                     rules={[
                       {
                         required: true,
-                        message: '请选择院区!',
+                        message: initialState?.customerType == '2'?'请选择园区!':'请选择院区!',
                       },
                     ]}
                   />
@@ -288,7 +289,7 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
             }}
           </ProFormDependency>
           <KCProSelect
-            label="医院等级"
+            label={initialState?.customerType == '2'?'园区等级':'医院等级'}
             width="md"
             name="hospitalLevel"
             request={async () => {
@@ -303,7 +304,7 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
             ]}
           />
           <KCProSelect
-            label="医院类型"
+            label={initialState?.customerType == '2'?'园区类型':'医院类型'}
             width="md"
             name="hospitalType"
             request={async () => {
@@ -318,7 +319,7 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
             ]}
           />
           <KCProSelect
-            label="医院性质"
+            label={initialState?.customerType == '2'?'园区性质':'医院性质'}
             width="md"
             name="hospitalNature"
             request={async () => {
@@ -333,7 +334,7 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
             ]}
           />
 
-          <ProFormText width="md" name="hospSign" label="医院标识" disabled />
+          <ProFormText width="md" name="hospSign" label={initialState?.customerType == '2'?'园区标识':'医院标识'} disabled />
           <ProFormText
             width="md"
             name="hospAbbreviation"

+ 421 - 446
src/pages/platform/setting/hospParamsMana/index.tsx

@@ -2,20 +2,18 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2023-03-03 11:30:33
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-12-06 16:44:24
+ * @LastEditTime: 2025-02-18 15:10:01
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/index.tsx
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
 
-
-
 import { KCInput } from '@/components/KCInput';
 import KCTable from '@/components/kcTable';
 
-import { ModalForm, ProFormDependency, ProFormInstance, ProFormSelect, ProFormText, ProFormTextArea } from '@ant-design/pro-form'
+import { ModalForm, ProFormDependency, ProFormInstance, ProFormSelect, ProFormText, ProFormTextArea } from '@ant-design/pro-form';
 import { ProColumns } from '@ant-design/pro-table';
 import { Input, message, Modal, Popconfirm, Switch, TreeProps } from 'antd';
-import React, { Key, useEffect, useRef, useState } from 'react'
+import React, { Key, useEffect, useRef, useState } from 'react';
 import { addData, delData, dicOpenStatHandle, editData, getData, getTreeData } from './service';
 
 import './style.less';
@@ -30,469 +28,446 @@ import { getDeepestTreeData } from '@/utils';
 import { getPubDicData } from '@/service/public';
 import { getCurrentHospAllDepartments, getCurrentHospAllEmps } from '@/service/hospList';
 
-
 const SearchIcon = createFromIconfontCN({
-    scriptUrl: '//at.alicdn.com/t/c/font_1927152_g1njmm1kh7b.js',
+  scriptUrl: '',
 });
 
-
 export default function PubDicTypeMana() {
-
-    const [tableDataFilterParams, set_tableDataFilterParams] = useState<any | undefined>();
-    const [tableDataSearchKeywords, set_tableDataSearchKeywords] = useState<string>('');
-    const [reload, set_reload] = useState(false);
-    const [currentEdit, set_currentEdit] = useState<any>(undefined);
-
-    const [treeData, set_treeData] = useState<any[]>([]);
-    const [currentSelectedTreeNode, set_currentSelectedTreeNode] = useState<any | undefined>();
-    const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
-    const [searchValue, setSearchValue] = useState('');
-    const [autoExpandParent, setAutoExpandParent] = useState(true);
-    const [defaultSelectedKeys,set_defaultSelectedKeys] = useState<Key[]>([]);
-
-
-
-
-    const columns: ProColumns[] = [
-        {
-            title: '参数等级',
-            dataIndex: 'levelName',
-
-        },
-        {
-            title: '作用对象',
-            dataIndex: 'targetName',
-        },
-        {
-            title: '参数值',
-            dataIndex: 'value',
-        },
-        {
-            title: '启用',
-            dataIndex: 'status',
-            hideInTable:currentSelectedTreeNode?.status != 1,
-            renderText(val, record, index, action) {
-                return <Switch size='small' checked={val? true : false} onChange={(bool) => chnageDicOpenStatHandle(bool, record)} />
-            },
-        },
-        {
-            title: '操作',
-            key: 'option',
-            width: 120,
-            hideInTable:currentSelectedTreeNode?.status != 1,
-            valueType: 'option',
-            render: (_: any, record: any) => {
-                return [
-                    <UpDataActBtn key={'act'} record={record} type='EDIT' />,
-                    <Popconfirm
-                        title="是否确认删除?"
-                        key="del"
-                        onConfirm={() => delTableData(record)}
-                    >
-                        <a>删除</a>
-                    </Popconfirm>
-                ]
-            },
-        },
-
-    ]
-
-
-    const chnageDicOpenStatHandle = async (bool: boolean, record: any) => {
-        const resp = await dicOpenStatHandle({ id: record.id, status: bool ? '1' : '0' });
-        if (resp) {
-            set_reload(true);
-        }
+  const [tableDataFilterParams, set_tableDataFilterParams] = useState<any | undefined>();
+  const [tableDataSearchKeywords, set_tableDataSearchKeywords] = useState<string>('');
+  const [reload, set_reload] = useState(false);
+  const [currentEdit, set_currentEdit] = useState<any>(undefined);
+
+  const [treeData, set_treeData] = useState<any[]>([]);
+  const [currentSelectedTreeNode, set_currentSelectedTreeNode] = useState<any | undefined>();
+  const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
+  const [searchValue, setSearchValue] = useState('');
+  const [autoExpandParent, setAutoExpandParent] = useState(true);
+  const [defaultSelectedKeys, set_defaultSelectedKeys] = useState<Key[]>([]);
+
+  const columns: ProColumns[] = [
+    {
+      title: '参数等级',
+      dataIndex: 'levelName',
+    },
+    {
+      title: '作用对象',
+      dataIndex: 'targetName',
+    },
+    {
+      title: '参数值',
+      dataIndex: 'value',
+    },
+    {
+      title: '启用',
+      dataIndex: 'status',
+      hideInTable: currentSelectedTreeNode?.status != 1,
+      renderText(val, record, index, action) {
+        return <Switch size="small" checked={val ? true : false} onChange={(bool) => chnageDicOpenStatHandle(bool, record)} />;
+      },
+    },
+    {
+      title: '操作',
+      key: 'option',
+      width: 120,
+      hideInTable: currentSelectedTreeNode?.status != 1,
+      valueType: 'option',
+      render: (_: any, record: any) => {
+        return [
+          <UpDataActBtn key={'act'} record={record} type="EDIT" />,
+          <Popconfirm title="是否确认删除?" key="del" onConfirm={() => delTableData(record)}>
+            <a>删除</a>
+          </Popconfirm>,
+        ];
+      },
+    },
+  ];
+
+  const chnageDicOpenStatHandle = async (bool: boolean, record: any) => {
+    const resp = await dicOpenStatHandle({ id: record.id, status: bool ? '1' : '0' });
+    if (resp) {
+      set_reload(true);
     }
-
-
-    const getTableData = async (params: any) => {
-        const resp = await getData({ ...params, parameterCode: currentSelectedTreeNode.code });
-        set_reload(false);
-        if (resp) {
-            return {
-                data: resp.list,
-                success: true,
-                total: resp.totalCount,
-            }
-        }
-        return []
+  };
+
+  const getTableData = async (params: any) => {
+    const resp = await getData({ ...params, parameterCode: currentSelectedTreeNode.code });
+    set_reload(false);
+    if (resp) {
+      return {
+        data: resp.list,
+        success: true,
+        total: resp.totalCount,
+      };
     }
-
-    const delTableData = async (record: any) => {
-        const resp = await delData(record.id);
-        if (resp) {
-            set_reload(true);
-            // message.success('操作成功!');
-        }
+    return [];
+  };
+
+  const delTableData = async (record: any) => {
+    const resp = await delData(record.id);
+    if (resp) {
+      set_reload(true);
+      // message.success('操作成功!');
     }
+  };
+
+  const updateTable = async (formVal: any, type: 'EDIT' | 'ADD') => {
+    if (type == 'ADD') {
+      const resp = await addData({
+        code: currentSelectedTreeNode.code,
+        level: formVal.level,
+        systemId: currentSelectedTreeNode.systemId,
+        targetCode: formVal.target.value,
+        targetName: formVal.target.label,
+        value: formVal.value,
+      });
+      if (resp) {
+        set_reload(true);
+      }
+    }
+    if (type == 'EDIT') {
+      const { id, status } = currentEdit;
+      const resp = await editData({
+        id: id,
+        value: formVal.value,
+        level: formVal.level,
+        status: status,
+        systemId: currentSelectedTreeNode.systemId,
+        targetCode: formVal.target.value,
+        targetName: formVal.target.label,
+      });
+      if (resp) {
+        set_reload(true);
+      }
+    }
+  };
 
-
-
-
-    const updateTable = async (formVal: any, type: 'EDIT' | "ADD") => {
-
-        if (type == 'ADD') {
-            const resp = await addData({
-                code:currentSelectedTreeNode.code,
-                level:formVal.level,
-                systemId:currentSelectedTreeNode.systemId,
-                targetCode:formVal.target.value,
-                targetName:formVal.target.label,
-                value:formVal.value
-            });
-            if (resp) {
-                set_reload(true);
-            }
+  const UpDataActBtn = ({ record, type }: { record: any; type: 'EDIT' | 'ADD' }) => {
+    const formRef = useRef<ProFormInstance>();
+    return (
+      <ModalForm
+        title={`${type == 'EDIT' ? '编辑' : '新增'}参数(${currentSelectedTreeNode && currentSelectedTreeNode.name})`}
+        width={352}
+        formRef={formRef}
+        initialValues={type == 'EDIT' ? { ...record, level: `${record.level}`, target: { label: record.targetName, value: record.targetCode } } : {}}
+        trigger={
+          type == 'EDIT' ? (
+            <a
+              key="edit"
+              onClick={(e) => {
+                e.stopPropagation();
+                set_currentEdit(record);
+              }}
+            >
+              编辑
+            </a>
+          ) : (
+            <span className="add">新增</span>
+          )
         }
-        if (type == 'EDIT') {
-            const { id,status } = currentEdit;
-            const resp = await editData({
-                id:id,
-                value:formVal.value,
-                level:formVal.level,
-                status:status,
-                systemId:currentSelectedTreeNode.systemId,
-                targetCode:formVal.target.value,
-                targetName:formVal.target.label,
-                
-            });
+        onFinish={(val) => {
+          return updateTable(val, type);
+        }}
+      >
+        <ProFormSelect
+          name="level"
+          label="参数等级:"
+          placeholder="请选择院区"
+          request={async () => {
+            const resp = await getPubDicData('PARAMS_LEVEL');
             if (resp) {
-                set_reload(true);
-            }
-        }
-
+              const data: any = resp.dataVoList?.map((a: any) => ({
+                label: a.name,
+                value: a.code,
+              }));
 
-    }
-
-    const UpDataActBtn = ({ record, type }: { record: any, type: 'EDIT' | 'ADD' }) => {
-        const formRef = useRef<ProFormInstance>();
-        return (
-            <ModalForm
-                title={`${type == 'EDIT' ? '编辑' : '新增'}参数(${currentSelectedTreeNode&&currentSelectedTreeNode.name})`}
-                width={352}
-                formRef={formRef}
-                initialValues={type == 'EDIT' ? { ...record,level:`${record.level}`,target:{label:record.targetName,value:record.targetCode} } : {}}
-                trigger={
-                    type == 'EDIT' ? <a key="edit" onClick={(e) =>{e.stopPropagation();set_currentEdit(record)} }>编辑</a> : <span className='add'>新增</span>
-                }
-                onFinish={(val) => {
-                    return updateTable(val, type);
-                }}
-            >
-                <ProFormSelect
-                    name="level"
-                    label="参数等级:"
-                    placeholder="请选择院区"
-                    request={async () => {
-                        const resp = await getPubDicData('PARAMS_LEVEL');
-                        if (resp) {
-                            const data: any = resp.dataVoList?.map((a: any) => ({
-                                label: a.name,
-                                value: a.code
-                            }));
-
-                            return data;
-                        }
-                        return []
-
-                    }}
-
-                    fieldProps={{
-                        size:'small',
-                        onChange(value, option) {
-                            formRef.current?.setFieldValue('target',undefined);
+              return data;
+            }
+            return [];
+          }}
+          fieldProps={{
+            size: 'small',
+            onChange(value, option) {
+              formRef.current?.setFieldValue('target', undefined);
+            },
+          }}
+          rules={[{ required: true, message: '参数等级不能为空!' }]}
+        />
+
+        <ProFormDependency name={['level']}>
+          {({ level }) => {
+            return (
+              <ProFormSelect
+                name="target"
+                label="作用对象:"
+                placeholder="请选择"
+                params={level}
+                rules={[{ required: true, message: '作用对象不能为空!' }]}
+                request={async () => {
+                  if (level == '1') {
+                    const currentSelectedHop = localStorage.getItem('currentSelectedSubHop');
+                    if (currentSelectedHop) {
+                      const { id, name } = JSON.parse(currentSelectedHop);
+                      return [
+                        {
+                          value: id,
+                          label: name,
                         },
-                    }}
-                    rules={[{ required: true, message: '参数等级不能为空!' }]}
-                />
-
-                <ProFormDependency name={['level']}>
-                    {
-                        ({ level }) => {
-                           
-                            return (
-                                <ProFormSelect
-                                    name="target"
-                                    label="作用对象:"
-                                    placeholder="请选择"
-                                    params={level}
-                                    rules={[{ required: true, message: '作用对象不能为空!' }]}
-                                    request={ async () => {
-                                        if (level == '1') {
-                                            const currentSelectedHop = localStorage.getItem('currentSelectedSubHop');
-                                            if (currentSelectedHop) {
-                                                const { id, name } = JSON.parse(currentSelectedHop);
-                                                return [{
-                                                    value: id,
-                                                    label: name
-                                                }]
-                                            }
-
-                                        }
-                                        if (level == '2') {
-                                            const resp = await getCurrentHospAllDepartments();
-                                            if (resp) {
-                                                const data: any = resp.map((a: any) => ({
-                                                    label: a.name,
-                                                    value: a.id
-                                                }));
-
-                                                return data;
-                                            }
-                                        }
-                                        if (level == '3') {
-                                            const resp = await getCurrentHospAllEmps();
-                                            if (resp) {
-                                                const data: any = resp.map((a: any) => ({
-                                                    label: a.name,
-                                                    value: a.id
-                                                }));
-
-                                                return data;
-                                            }
-                                        }
-                                        return []
-
-                                    }}
-
-                                    fieldProps={{
-                                        size:'small',
-                                          labelInValue:true
-                                    }}
-                                />
-                            )
-                        }
+                      ];
                     }
-                </ProFormDependency>
-
-                <ProFormTextArea
-                    name="value"
-                    label="参数值:"
-                    placeholder="请输入"
-                    rules={[{ required: true, message: '参数值不能为空!' }]}
-
-                />
-            </ModalForm>
-        )
-    }
-
-
-
-
-    const tableDataSearchHandle = (paramName: string) => {
-
-
-        set_tableDataFilterParams({
-            ...tableDataFilterParams,
-            current:1,
-            [`${paramName}`]: tableDataSearchKeywords
-        })
+                  }
+                  if (level == '2') {
+                    const resp = await getCurrentHospAllDepartments();
+                    if (resp) {
+                      const data: any = resp.map((a: any) => ({
+                        label: a.name,
+                        value: a.id,
+                      }));
+
+                      return data;
+                    }
+                  }
+                  if (level == '3') {
+                    const resp = await getCurrentHospAllEmps();
+                    if (resp) {
+                      const data: any = resp.map((a: any) => ({
+                        label: a.name,
+                        value: a.id,
+                      }));
+
+                      return data;
+                    }
+                  }
+                  return [];
+                }}
+                fieldProps={{
+                  size: 'small',
+                  labelInValue: true,
+                }}
+              />
+            );
+          }}
+        </ProFormDependency>
+
+        <ProFormTextArea name="value" label="参数值:" placeholder="请输入" rules={[{ required: true, message: '参数值不能为空!' }]} />
+      </ModalForm>
+    );
+  };
+
+  const tableDataSearchHandle = (paramName: string) => {
+    set_tableDataFilterParams({
+      ...tableDataFilterParams,
+      current: 1,
+      [`${paramName}`]: tableDataSearchKeywords,
+    });
+  };
+
+  const getTreeReqFunc = async (name?: string) => {
+    const resp = await getTreeData();
+    if (resp) {
+      const treeData = resp.map((a: any) => ({
+        ...a,
+        name: a.systemName,
+        id: Math.random(),
+        children: a.parameters,
+      }));
+      set_treeData(treeData);
     }
-
-    const getTreeReqFunc = async (name?: string) => {
-        const resp = await getTreeData();
-        if (resp) {
-            const treeData = resp.map((a: any) => ({
-                ...a,
-                name: a.systemName,
-                id:Math.random(),
-                children: a.parameters
-            }));
-            set_treeData(treeData);
+  };
+
+  const dataList: any[] = [];
+
+  const getParentKey = (key: React.Key, tree: any[]): React.Key => {
+    let parentKey: React.Key;
+    for (let i = 0; i < tree.length; i++) {
+      const node = tree[i];
+      if (node.children) {
+        if (node.children.some((item: { id: React.Key }) => item.id === key)) {
+          parentKey = node.id;
+        } else if (getParentKey(key, node.children)) {
+          parentKey = getParentKey(key, node.children);
         }
+      }
     }
-
-
-    const dataList: any[] = [];
-
-    const getParentKey = (key: React.Key, tree: any[]): React.Key => {
-        let parentKey: React.Key;
-        for (let i = 0; i < tree.length; i++) {
-            const node = tree[i];
-            if (node.children) {
-                if (node.children.some((item: { id: React.Key; }) => item.id === key)) {
-                    parentKey = node.id;
-                } else if (getParentKey(key, node.children)) {
-                    parentKey = getParentKey(key, node.children);
-                }
-            }
-        }
-        return parentKey!;
-    };
-
-    const generateList = (data: any[]) => {
-        for (let i = 0; i < data.length; i++) {
-            const node = data[i];
-            const { id, name } = node;
-            dataList.push({ id, name: name });
-            if (node.children) {
-                generateList(node.children);
-            }
-        }
-    };
-    generateList(treeData as any);
-
-    const onTreeSearchKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-
-        const { value } = e.target;
-        const newExpandedKeys = dataList
-            .map((item) => {
-                if (item.name.indexOf(value) > -1) {
-                    return getParentKey(item.id, treeData);
-                }
-                return null;
-            });
-
-        const b = newExpandedKeys.filter((item, i, self) => item && self.indexOf(item) === i);
-        setExpandedKeys(newExpandedKeys as React.Key[]);
-        setSearchValue(value);
-        setAutoExpandParent(true);
+    return parentKey!;
+  };
+
+  const generateList = (data: any[]) => {
+    for (let i = 0; i < data.length; i++) {
+      const node = data[i];
+      const { id, name } = node;
+      dataList.push({ id, name: name });
+      if (node.children) {
+        generateList(node.children);
+      }
     }
+  };
+  generateList(treeData as any);
+
+  const onTreeSearchKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+    const { value } = e.target;
+    const newExpandedKeys = dataList.map((item) => {
+      if (item.name.indexOf(value) > -1) {
+        return getParentKey(item.id, treeData);
+      }
+      return null;
+    });
+
+    const b = newExpandedKeys.filter((item, i, self) => item && self.indexOf(item) === i);
+    setExpandedKeys(newExpandedKeys as React.Key[]);
+    setSearchValue(value);
+    setAutoExpandParent(true);
+  };
+
+  const onSelect: TreeProps['onSelect'] = (selectedKeys, info) => {
+    //console.log('selected', selectedKeys, info);
+    const { node } = info;
+    if (!node.children) {
+      set_currentSelectedTreeNode(node);
+    }
+  };
 
-    const onSelect: TreeProps['onSelect'] = (selectedKeys, info) => {
-        //console.log('selected', selectedKeys, info);
-        const { node } = info;
-        if (!node.children) {
-            set_currentSelectedTreeNode(node);
-        }
-    };
-
-    const onExpand = (newExpandedKeys: React.Key[]) => {
-        setExpandedKeys(newExpandedKeys);
-        setAutoExpandParent(false);
-    };
-
-    useEffect(() => {
-        if (currentSelectedTreeNode) {
-            set_tableDataFilterParams({ ...tableDataFilterParams, systemId: currentSelectedTreeNode.code });
-        }
-
-    }, [currentSelectedTreeNode]);
-
-    useEffect(() => {
-        //初始化左侧树结构数据后
-        if (treeData?.length > 0) {
-
-            if (treeData[0].children && treeData[0].children.length > 0) {
-                const [node, nodeParent] = getDeepestTreeData(treeData[0], 'children');
-
-                set_currentSelectedTreeNode(node);
-                setExpandedKeys([nodeParent.id]);
-                set_defaultSelectedKeys(node.id);
-                console.log({node,treeData});
-            }
-        }
-    }, [treeData]);
-
+  const onExpand = (newExpandedKeys: React.Key[]) => {
+    setExpandedKeys(newExpandedKeys);
+    setAutoExpandParent(false);
+  };
 
-    useEffect(() => {
-        getTreeReqFunc();
-    }, []);
+  useEffect(() => {
+    if (currentSelectedTreeNode) {
+      set_tableDataFilterParams({ ...tableDataFilterParams, systemId: currentSelectedTreeNode.code });
+    }
+  }, [currentSelectedTreeNode]);
+
+  useEffect(() => {
+    //初始化左侧树结构数据后
+    if (treeData?.length > 0) {
+      if (treeData[0].children && treeData[0].children.length > 0) {
+        const [node, nodeParent] = getDeepestTreeData(treeData[0], 'children');
+
+        set_currentSelectedTreeNode(node);
+        setExpandedKeys([nodeParent.id]);
+        set_defaultSelectedKeys(node.id);
+        console.log({ node, treeData });
+      }
+    }
+  }, [treeData]);
 
+  useEffect(() => {
+    getTreeReqFunc();
+  }, []);
 
-    return (
-        <div className='PubDicTypeMana'>
-            <div className='left'>
-                <div className='search'>
-                    <Input
-                        className='searchInput'
-                        placeholder="请输入"
-                        size='small'
-                        allowClear
-
-                        style={{ marginBottom: 16 }}
-                        onChange={onTreeSearchKeyChange}
-                        suffix={
-                            <SearchIcon type='iconsousuo' />
-                        }
-                    />
-                </div>
-                {
-                    treeData && treeData.length > 0 && (
-                        <DirectoryTree
-                            fieldNames={{ title: 'name', key: 'id' }}
-                            rootStyle={{ height: '100%', paddingBottom: 50, overflowY: 'scroll', overflowX: 'hidden' }}
-                            onSelect={onSelect}
-                            onExpand={onExpand}
-                            expandedKeys={expandedKeys}
-                            autoExpandParent={autoExpandParent}
-                            selectedKeys={currentSelectedTreeNode ? [currentSelectedTreeNode.id] : []}
-                            blockNode={true}
-                            icon={() => null}
-                            titleRender={
-                                (nodeData: any) => {
-                                    const strTitle = nodeData.name as string;
-                                    const index = strTitle.indexOf(searchValue);
-                                    const beforeStr = strTitle.substring(0, index);
-                                    const afterStr = strTitle.slice(index + searchValue.length);
-
-                                    const title =
-                                        index > -1 ? (
-                                            <span>
-                                                {beforeStr}
-                                                <span className="site-tree-search-value" style={{ color: 'red', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{searchValue}</span>
-                                                {afterStr}
-                                            </span>
-                                        ) : (
-                                            <span className='strTitle'>{strTitle}</span>
-                                        );
-                                    return <div style={{
-                                        display: 'flex', flexDirection: 'row',
-                                        width: '100%',
-                                        justifyContent: 'flex-start', alignItems: 'center', height: 32,
-                                        borderRadius: '4px',
-                                        overflow: 'hidden',
-                                        color: '#17181A',
-                                        textOverflow: 'ellipsis',
-                                        whiteSpace: 'nowrap'
-
-                                    }}>{title}</div>
-                                }
-                            }
-                            defaultSelectedKeys={defaultSelectedKeys}
-                            treeData={treeData as unknown as DataNode[]}
-                            // treeData={treeDataNew}
-                            switcherIcon={(props: any) => {
-                                const { expanded } = props;
-                                //return <button className='site-table-row-expand-icon site-table-row-expand-icon-expanded'></button>
-                                return !expanded ? <img style={{ width: 20, height: 20, position: 'relative', top: 5 }} src={expandedIcon} /> : <img style={{ width: 20, height: 20, position: 'relative', top: 5 }} src={closeIcon} />
-                            }}
-                        />
-                    )
-                }
-            </div>
-            <div className='right'>
-                <div className='rightTitle'>{currentSelectedTreeNode&&currentSelectedTreeNode.name}</div>
-                <div className='descr'>{`默认值:${currentSelectedTreeNode?currentSelectedTreeNode.value:''}  丨  参数描述:${currentSelectedTreeNode?currentSelectedTreeNode.description:''}`}</div>
-                <div className='toolBar'>
-                    <div className='filter'>
-                        <div className='filterItem'>
-                            <span className='label'>检索:</span>
-                            <KCInput placeholder={'请输入名称'} style={{ width: 160 }} search allowClear
-                                onChange={(e) => {
-                                    set_tableDataSearchKeywords(e.target.value);
-                                    if (e.target.value.length == 0) {
-                                        set_tableDataFilterParams({
-                                            ...tableDataFilterParams,
-                                            current:1,
-                                            targetName: ''
-                                        });
-                                    }
-                                }}
-                                onSearch={() => tableDataSearchHandle('targetName')}
-
-                            />
-                        </div>
-                    </div>
-                    <div className='btnGroup'>
-                        {currentSelectedTreeNode?.status == 1&&<UpDataActBtn record type='ADD' />}
-                    </div>
-                </div>
-                <div style={{ marginTop: 16 }}>
-                    {currentSelectedTreeNode && <KCTable scroll={{y:`calc(100vh - 315px)`}} columns={columns as ProColumns[]} reload={reload} rowKey='id' newVer params={tableDataFilterParams} request={(params) => getTableData(params)} />}
+  return (
+    <div className="PubDicTypeMana">
+      <div className="left">
+        <div className="search">
+          <Input className="searchInput" placeholder="请输入" size="small" allowClear style={{ marginBottom: 16 }} onChange={onTreeSearchKeyChange} suffix={<SearchIcon type="iconsousuo" />} />
+        </div>
+        {treeData && treeData.length > 0 && (
+          <DirectoryTree
+            fieldNames={{ title: 'name', key: 'id' }}
+            rootStyle={{ height: '100%', paddingBottom: 50, overflowY: 'scroll', overflowX: 'hidden' }}
+            onSelect={onSelect}
+            onExpand={onExpand}
+            expandedKeys={expandedKeys}
+            autoExpandParent={autoExpandParent}
+            selectedKeys={currentSelectedTreeNode ? [currentSelectedTreeNode.id] : []}
+            blockNode={true}
+            icon={() => null}
+            titleRender={(nodeData: any) => {
+              const strTitle = nodeData.name as string;
+              const index = strTitle.indexOf(searchValue);
+              const beforeStr = strTitle.substring(0, index);
+              const afterStr = strTitle.slice(index + searchValue.length);
+
+              const title =
+                index > -1 ? (
+                  <span>
+                    {beforeStr}
+                    <span className="site-tree-search-value" style={{ color: 'red', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
+                      {searchValue}
+                    </span>
+                    {afterStr}
+                  </span>
+                ) : (
+                  <span className="strTitle">{strTitle}</span>
+                );
+              return (
+                <div
+                  style={{
+                    display: 'flex',
+                    flexDirection: 'row',
+                    width: '100%',
+                    justifyContent: 'flex-start',
+                    alignItems: 'center',
+                    height: 32,
+                    borderRadius: '4px',
+                    overflow: 'hidden',
+                    color: '#17181A',
+                    textOverflow: 'ellipsis',
+                    whiteSpace: 'nowrap',
+                  }}
+                >
+                  {title}
                 </div>
+              );
+            }}
+            defaultSelectedKeys={defaultSelectedKeys}
+            treeData={treeData as unknown as DataNode[]}
+            // treeData={treeDataNew}
+            switcherIcon={(props: any) => {
+              const { expanded } = props;
+              //return <button className='site-table-row-expand-icon site-table-row-expand-icon-expanded'></button>
+              return !expanded ? (
+                <img style={{ width: 20, height: 20, position: 'relative', top: 5 }} src={expandedIcon} />
+              ) : (
+                <img style={{ width: 20, height: 20, position: 'relative', top: 5 }} src={closeIcon} />
+              );
+            }}
+          />
+        )}
+      </div>
+      <div className="right">
+        <div className="rightTitle">{currentSelectedTreeNode && currentSelectedTreeNode.name}</div>
+        <div className="descr">{`默认值:${currentSelectedTreeNode ? currentSelectedTreeNode.value : ''}  丨  参数描述:${currentSelectedTreeNode ? currentSelectedTreeNode.description : ''}`}</div>
+        <div className="toolBar">
+          <div className="filter">
+            <div className="filterItem">
+              <span className="label">检索:</span>
+              <KCInput
+                placeholder={'请输入名称'}
+                style={{ width: 160 }}
+                search
+                allowClear
+                onChange={(e) => {
+                  set_tableDataSearchKeywords(e.target.value);
+                  if (e.target.value.length == 0) {
+                    set_tableDataFilterParams({
+                      ...tableDataFilterParams,
+                      current: 1,
+                      targetName: '',
+                    });
+                  }
+                }}
+                onSearch={() => tableDataSearchHandle('targetName')}
+              />
             </div>
+          </div>
+          <div className="btnGroup">{currentSelectedTreeNode?.status == 1 && <UpDataActBtn record type="ADD" />}</div>
+        </div>
+        <div style={{ marginTop: 16 }}>
+          {currentSelectedTreeNode && (
+            <KCTable
+              scroll={{ y: `calc(100vh - 315px)` }}
+              columns={columns as ProColumns[]}
+              reload={reload}
+              rowKey="id"
+              newVer
+              params={tableDataFilterParams}
+              request={(params) => getTableData(params)}
+            />
+          )}
         </div>
-    )
+      </div>
+    </div>
+  );
 }

+ 1133 - 0
src/pages/platform/setting/indicatoLagacy/ DrawerForm/drawer.tsx

@@ -0,0 +1,1133 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2022-07-13 15:27:51
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-02-28 18:15:16
+ * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/indicatorMana/drawer.tsx
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+
+import React, { useEffect, useRef, useState } from 'react';
+import { Drawer, Cascader, Button, Form } from 'antd';
+import ProForm, { ProFormCascader, ProFormDependency, ProFormDigit, ProFormInstance, ProFormRadio, ProFormSelect, ProFormText, ProFormTextArea } from '@ant-design/pro-form';
+
+import './style.less';
+import { getIndicatorDictionary, IndicatorDictionaryDataType } from '@/service/dictionary';
+import { DefaultOptionType } from 'antd/lib/select';
+import { getHospDepartment } from '@/service/department';
+import { getUsers } from '@/service/user';
+import { addIndicatorManaList } from '@/service/indicator';
+import { createFromIconfontCN } from '@ant-design/icons';
+
+export interface IndicatorDrawerForm {
+  visible: boolean;
+  record?: any;
+  onVisibleChange?: (bool: boolean) => void;
+  onFinish?: (formData: any) => void;
+  onChange?: (data: any) => void;
+  actType: 'EDIT' | 'ADD';
+}
+
+const IconFont = createFromIconfontCN({
+  scriptUrl: '',
+});
+
+const DrawerForm = (props: IndicatorDrawerForm) => {
+  const { visible = false, onChange, onFinish, record, onVisibleChange, actType } = props;
+
+  const [_visible, _setVisible] = useState(false);
+
+  const [renderType, set_renderType] = useState<'ADD' | 'EDIT'>('ADD');
+
+  const [dirData, set_dirData] = useState<IndicatorDictionaryDataType[]>([]);
+
+  const baseInfoformRef = useRef<ProFormInstance>();
+  const manaInfoformRef = useRef<ProFormInstance>();
+  const showSetInfoformRef = useRef<ProFormInstance>();
+  const adminInfoformRef = useRef<ProFormInstance>();
+
+  const showDrawer = () => {
+    _setVisible(true);
+    onVisibleChange && onVisibleChange(true);
+  };
+
+  const onClose = () => {
+    _setVisible(false);
+    onVisibleChange && onVisibleChange(false);
+  };
+
+  const commitHandle = () => {
+    let commitData: any[] = [];
+    if (baseInfoformRef.current && baseInfoformRef.current.validateFieldsReturnFormatValue) {
+      baseInfoformRef.current?.validateFieldsReturnFormatValue().then((val) => {
+        commitData.push({
+          baseInfoformRef: val,
+        });
+      });
+    }
+
+    if (manaInfoformRef.current && manaInfoformRef.current.validateFieldsReturnFormatValue) {
+      manaInfoformRef.current?.validateFieldsReturnFormatValue().then((val) => {
+        console.log('manaInfoformRef', val);
+        //_indicatorTypeList
+        // manaInfoformRef.current?.getFieldsValue();
+        // console.log('w',manaInfoformRef.current?.getFieldValue('_indicatorTypeList'))
+        const _indicatorTypeLists = manaInfoformRef.current?.getFieldValue('_indicatorTypeList');
+        const _indicatorExternalList = manaInfoformRef.current?.getFieldValue('_indicatorExternalList');
+
+        // console.log({_indicatorTypeLists,_indicatorExternalList});
+
+        commitData.push({
+          manaInfoformRef: {
+            ...val,
+            indicatorTypeList: _indicatorTypeLists ? _indicatorTypeLists : val.indicatorTypeList ? val.indicatorTypeList.map((t: any) => t[t.length - 1]) : [],
+            indicatorExternalList: _indicatorExternalList ? _indicatorExternalList : val.indicatorExternalList ? val.indicatorExternalList.map((t: any) => t[t.length - 1]) : [],
+          },
+        });
+      });
+    }
+    if (showSetInfoformRef.current && showSetInfoformRef.current.validateFieldsReturnFormatValue) {
+      showSetInfoformRef.current?.validateFieldsReturnFormatValue().then((val) => {
+        // console.log('showSetInfoformRef',val);
+        commitData.push({
+          showSetInfoformRef: val,
+        });
+      });
+    }
+    if (adminInfoformRef.current && adminInfoformRef.current.validateFieldsReturnFormatValue) {
+      adminInfoformRef.current?.validateFieldsReturnFormatValue().then((val) => {
+        const _indicatorMenuList = adminInfoformRef.current?.getFieldValue('_indicatorMenuList');
+        if (val.indicatorMenuList) {
+          commitData.push({
+            adminInfoformRef: {
+              ...val,
+              indicatorMenuList: _indicatorMenuList ? _indicatorMenuList : val.indicatorMenuList ? val.indicatorMenuList.map((t: any) => t[t.length - 1]) : [],
+            },
+          });
+        }
+      });
+    }
+
+    setTimeout(() => {
+      const hasBaseInfoformRef = commitData.some((item) => item.hasOwnProperty('baseInfoformRef'));
+      const hasAdminInfoformRef = commitData.some((item) => item.hasOwnProperty('adminInfoformRef'));
+      if (hasBaseInfoformRef && hasAdminInfoformRef) {
+        onFinish && onFinish(commitData);
+      }
+    }, 1000);
+  };
+
+  const getDataFromDir = async () => {
+    const dirData = await getIndicatorDictionary();
+    set_dirData(dirData);
+  };
+
+  const getSelectorData = (code: string, type?: 'Cascader') => {
+    const result = dirData.filter((t) => t.code == code);
+
+    if (type == 'Cascader' && result.length > 0) {
+      return result[0].children;
+    }
+
+    if (result.length > 0) {
+      return result[0].children.map((t) => ({ label: t.name, value: t.code }));
+    }
+    return [];
+  };
+
+  const setFormInit = (key: string) => {
+    if (key == 'indicatorTypeList') {
+      if (record && record.indicatorTypeList) {
+        const indicatorTypeListVal = record.indicatorTypeList ? record.indicatorTypeList.map((t: string) => t.split('/')) : [];
+        const _indicatorTypeList = indicatorTypeListVal.map((t: any) => t.slice(1, t.length));
+        // console.log({indicatorTypeListVal,_indicatorTypeList})
+        return _indicatorTypeList;
+      }
+    }
+    if (key == 'indicatorMenuList') {
+      if (record && record.indicatorMenuList) {
+        const a = record.indicatorMenuList ? record.indicatorMenuList.map((t: string) => t.split('/')) : [];
+        const b = a.map((t: any) => t.slice(1, t.length));
+        // console.log({a,b})
+        return b;
+      }
+    }
+
+    if (key == 'indicatorExternalList') {
+      if (record && record.indicatorExternalList) {
+        const a = record.indicatorExternalList ? record.indicatorExternalList.map((t: string) => t.split('/')) : [];
+        const b = a.map((t: any) => t.slice(1, t.length));
+        // console.log({a,b})
+        return b;
+      }
+    }
+  };
+
+  useEffect(() => {
+    if (visible) {
+      getDataFromDir();
+    }
+    _setVisible(visible);
+  }, [visible]);
+
+  useEffect(() => {
+    set_renderType(actType);
+  }, [actType]);
+
+  return (
+    <div>
+      <Drawer
+        className="DrawerForm"
+        width={1000}
+        title={renderType == 'ADD' ? '新增指标' : '管理信息'}
+        placement="right"
+        onClose={onClose}
+        open={_visible}
+        destroyOnClose
+        footer={false}
+        headerStyle={{ display: 'none' }}
+        // footer={
+        //     <div>
+        //         <Button style={{ marginRight: 20 }} onClick={() => onClose()}>取消</Button>
+        //         <Button type="primary" onClick={() => commitHandle()}>确定</Button>
+        //     </div>
+        // }
+        bodyStyle={{ background: 'rgba(153, 166, 191, 0.1)', padding: 0, scrollbarWidth: 'thin' }}
+      >
+        <div className="drawerHeader">
+          <div className="drawerTitle">
+            <svg
+              style={{ marginRight: 14, position: 'relative', top: 4, cursor: 'pointer' }}
+              onClick={() => _setVisible(false)}
+              viewBox="0 0 1024 1024"
+              version="1.1"
+              xmlns="http://www.w3.org/2000/svg"
+              p-id="41844"
+              width="18"
+              height="18"
+            >
+              <path
+                d="M793.024 200.96l27.392 27.392a12.8 12.8 0 0 1 0 18.112L555.52 510.784l267.392 266.752a12.8 12.8 0 0 1 0 18.112l-27.392 27.328a12.8 12.8 0 0 1-18.112 0L510.08 556.224l-263.424 262.784a12.8 12.8 0 0 1-18.112 0l-27.392-27.392a12.8 12.8 0 0 1 0-18.112l263.36-262.72-260.864-260.288a12.8 12.8 0 0 1 0-18.112l27.392-27.392a12.8 12.8 0 0 1 18.048 0L510.08 465.344l264.96-264.32a12.8 12.8 0 0 1 18.048 0z"
+                p-id="41845"
+                fill="#17181A"
+              ></path>
+            </svg>
+            {/* <IconFont style={{cursor:'pointer',marginRight:14,fontSize:16,color:'red'}} type='iconquxiao'  /> */}
+            {record && record.name}
+          </div>
+          <div className="btnGroup">
+            <span onClick={() => onClose()}>取消</span>
+            <span className="primary" onClick={() => commitHandle()}>
+              确定
+            </span>
+          </div>
+        </div>
+        <div className="drawerContent">
+          <div className="card">
+            <div className="cardTitle">指标基本信息</div>
+            <div className="formContent">
+              <ProForm
+                layout={'vertical'}
+                formRef={baseInfoformRef}
+                grid={true}
+                initialValues={renderType == 'ADD' ? { attributeType: 1 } : { ...record }}
+                rowProps={{
+                  gutter: [16, 0],
+                }}
+                submitter={{
+                  render: () => null,
+                }}
+              >
+                <ProFormText colProps={{ md: 12, xl: 8 }} name="name" label="指标名称" placeholder="请输入" fieldProps={{}} rules={[{ required: true, message: '这是必填项' }]} />
+                <ProFormText colProps={{ md: 12, xl: 8 }} name="code" label="指标编码" placeholder="请输入" rules={[{ required: true, message: '这是必填项' }]} />
+                <ProFormText colProps={{ md: 12, xl: 8 }} name="nationalCode" label="国家编码" placeholder="请输入" />
+                <ProFormTextArea
+                  colProps={{ md: 12, xl: 16 }}
+                  name="targetDefinition"
+                  label="指标定义"
+                  placeholder="请输入"
+                  rules={[{ required: true, message: '这是必填项' }]}
+                  fieldProps={{
+                    allowClear: true,
+                    autoSize: true,
+                  }}
+                />
+                <ProFormSelect
+                  colProps={{ md: 12, xl: 8 }}
+                  name="dimension"
+                  label="指标维度"
+                  fieldProps={{ size: 'small' }}
+                  options={[
+                    {
+                      label: '结构面',
+                      value: 1,
+                    },
+                    {
+                      label: '过程面',
+                      value: 2,
+                    },
+                    {
+                      label: '结果面',
+                      value: 3,
+                    },
+                  ]}
+                  placeholder="请选择"
+                />
+                <ProFormRadio.Group
+                  name="attributeType"
+                  label="指标属性"
+                  colProps={{ md: 12, xl: 24 }}
+                  options={[
+                    {
+                      label: '定性',
+                      value: 1,
+                    },
+                    {
+                      label: '定量',
+                      value: 2,
+                    },
+                  ]}
+                />
+                <ProFormDependency name={['attributeType']}>
+                  {({ attributeType }) => {
+                    if (attributeType == 2) {
+                      return (
+                        <>
+                          <ProFormSelect
+                            colProps={{ md: 12, xl: 8 }}
+                            name="directionType"
+                            label="管理指向"
+                            fieldProps={{ size: 'small' }}
+                            options={[
+                              {
+                                label: '正向',
+                                value: 1,
+                              },
+                              {
+                                label: '负向',
+                                value: 2,
+                              },
+                              {
+                                label: '区间',
+                                value: 3,
+                              },
+                              {
+                                label: '监测达标',
+                                value: 4,
+                              },
+                              {
+                                label: '监测比较',
+                                value: 5,
+                              },
+                            ]}
+                            placeholder="请选择"
+                          />
+                          <ProForm.Group colProps={{ md: 12, xl: 8 }}>
+                            <div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }}>
+                              <div>
+                                <ProFormDigit label="指标区间" name="lowerLimit" min={0} max={10000} placeholder="最小值" fieldProps={{ precision: 3 }} />
+                              </div>
+                              -
+                              <div style={{ position: 'relative', top: 10 }}>
+                                <ProFormDigit name="upperLimit" min={0} max={10000} placeholder="最大值" fieldProps={{ precision: 3 }} />
+                              </div>
+                            </div>
+                          </ProForm.Group>
+
+                          <ProFormSelect
+                            colProps={{ md: 12, xl: 8 }}
+                            name="unit"
+                            label="单位"
+                            fieldProps={{ size: 'small' }}
+                            request={async () => {
+                              const dirData = await getIndicatorDictionary();
+                              const result = dirData.filter((t) => t.code == 'Department');
+
+                              if (result.length > 0) {
+                                return result[0].children.map((t) => ({ label: t.name, value: t.code }));
+                              }
+                              return [];
+                            }}
+                            placeholder="请选择"
+                          />
+
+                          <ProFormDependency name={['unit']}>
+                            {({ unit }) => {
+                              return unit == '173' || unit == '326' ? (
+                                <ProForm.Group colProps={{ md: 12, xl: 24 }}>
+                                  <div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }}>
+                                    <div>
+                                      <ProFormText name="molecule" label="公式" width={437} placeholder="请输入分子" />
+                                    </div>
+                                    /
+                                    <div style={{ position: 'relative', top: 10 }}>
+                                      <ProFormText width={437} name="denominator" placeholder="请输入分母" />
+                                    </div>
+                                    {unit == '173' ? `*100%` : `‰`}
+                                  </div>
+                                </ProForm.Group>
+                              ) : (
+                                <ProForm.Group colProps={{ md: 12, xl: 24 }}>
+                                  <ProFormText name="molecule" label="公式" placeholder="请输入公式" />
+                                </ProForm.Group>
+                              );
+                            }}
+                          </ProFormDependency>
+
+                          <ProFormSelect
+                            colProps={{ md: 12, xl: 8 }}
+                            name="moleculeSourcType"
+                            label="分子数据来源"
+                            fieldProps={{ size: 'small' }}
+                            options={[
+                              {
+                                label: '定期填报',
+                                value: 1,
+                              },
+                              {
+                                label: '表单导入',
+                                value: 2,
+                              },
+                              {
+                                label: '系统导入',
+                                value: 3,
+                              },
+                              {
+                                label: '异动时填报',
+                                value: 4,
+                              },
+                            ]}
+                            placeholder="请选择"
+                          />
+                          {/* <ProFormText colProps={{ md: 12, xl: 8 }} name="moleculeSourcDesc" label='分子来源说明' placeholder="请输入" /> */}
+                          <ProFormTextArea
+                            colProps={{ md: 12, xl: 16 }}
+                            name="moleculeSourcDesc"
+                            label="分子来源说明"
+                            placeholder="请输入"
+                            fieldProps={{
+                              allowClear: true,
+                              autoSize: true,
+                              size: 'small',
+                            }}
+                          />
+                          <ProFormSelect
+                            colProps={{ md: 12, xl: 8 }}
+                            name="denominatorSourceType"
+                            label="分母数据来源"
+                            fieldProps={{ size: 'small' }}
+                            options={[
+                              {
+                                label: '定期填报',
+                                value: 1,
+                              },
+                              {
+                                label: '表单导入',
+                                value: 2,
+                              },
+                              {
+                                label: '系统导入',
+                                value: 3,
+                              },
+                              {
+                                label: '异动时填报',
+                                value: 4,
+                              },
+                            ]}
+                            placeholder="请选择"
+                          />
+
+                          {/* <ProFormText colProps={{ md: 12, xl: 8 }} name="denominatorSourceDesc" label='分母来源说明' placeholder="请输入" /> */}
+                          <ProFormTextArea
+                            colProps={{ md: 12, xl: 16 }}
+                            name="denominatorSourceDesc"
+                            label="分母来源说明"
+                            placeholder="请输入"
+                            fieldProps={{
+                              allowClear: true,
+                              autoSize: true,
+                            }}
+                          />
+                        </>
+                      );
+                    }
+                  }}
+                </ProFormDependency>
+              </ProForm>
+            </div>
+          </div>
+
+          <div className="card">
+            <div className="cardTitle">指标管理信息</div>
+            <div className="formContent">
+              <ProForm
+                formRef={manaInfoformRef}
+                layout={'vertical'}
+                autoFocusFirstInput={false}
+                grid={true}
+                initialValues={
+                  renderType == 'ADD'
+                    ? { indicatorTypeList: [], indicatorExternalList: [] }
+                    : {
+                        ...record,
+                        indicatorMenuList: setFormInit('indicatorMenuList'),
+                        // indicatorTypeList:record.indicatorTypeList?record.indicatorTypeList.map((t:string)=>t.split('/')).slice(1,record.indicatorTypeList.map((t:string)=>t.split('/')).length - 1):[],
+                        indicatorTypeList: setFormInit('indicatorTypeList'),
+                        indicatorExternalList: setFormInit('indicatorExternalList'),
+                        // indicatorCommitteeList:record.indicatorCommitteeList[0]?record.indicatorCommitteeList[0].split('/'):[]
+                      }
+                }
+                rowProps={{
+                  gutter: [16, 0],
+                }}
+                submitter={{
+                  render: () => null,
+                }}
+              >
+                <ProFormSelect
+                  colProps={{ md: 12, xl: 8 }}
+                  name="divisionId"
+                  label="负责科室"
+                  fieldProps={{ size: 'small' }}
+                  request={async () => {
+                    const data = await getHospDepartment();
+                    return data.map((t) => ({ label: t.name, value: t.code }));
+                  }}
+                  placeholder="请选择"
+                />
+                <ProFormSelect
+                  colProps={{ md: 12, xl: 8 }}
+                  name="principalId"
+                  label="负责人"
+                  fieldProps={{ size: 'small' }}
+                  request={async () => {
+                    const data = await getUsers({ pageSize: 1000, current: 1 });
+                    return data.list.map((t) => ({ label: t.name, value: t.code }));
+                  }}
+                  placeholder="请选择"
+                />
+                {/* <ProFormSelect
+                                colProps={{ md: 12, xl: 8 }}
+                                name="principalId"
+                                label="负责人"
+                                request={async ()=>{
+                                    const data = await getUsers({pageSize:1000,current:1});
+                                       return data.list.map(t=>({label:t.name,value:t.code}))
+                                }}
+                                placeholder="请选择"
+                            /> */}
+
+                <ProFormSelect
+                  colProps={{ md: 12, xl: 8 }}
+                  name="manageLevel"
+                  label="指标管理级别"
+                  fieldProps={{
+                    // mode: 'multiple',
+                    size: 'small',
+                    maxTagCount: 'responsive',
+                  }}
+                  options={[
+                    {
+                      label: '院级指标/日常',
+                      value: 0,
+                    },
+                    {
+                      label: '院级指标/重点监测',
+                      value: 1,
+                    },
+                    {
+                      label: '科级指标/日常',
+                      value: 2,
+                    },
+                    {
+                      label: '科级指标/重点监测',
+                      value: 3,
+                    },
+                  ]}
+                  placeholder="请选择"
+                />
+                <ProFormSelect
+                  colProps={{ md: 12, xl: 8 }}
+                  name="analysisType"
+                  label="分析方式"
+                  fieldProps={{ size: 'small' }}
+                  options={[
+                    {
+                      label: '数值',
+                      value: 1,
+                    },
+                    {
+                      label: '监测比较',
+                      value: 2,
+                    },
+                    {
+                      label: '目标值达标分析',
+                      value: 3,
+                    },
+                    {
+                      label: '数值趋势分析',
+                      value: 4,
+                    },
+                    {
+                      label: '标准差异常分析',
+                      value: 5,
+                    },
+                    {
+                      label: '四分位数值分析',
+                      value: 6,
+                    },
+                  ]}
+                  placeholder="请选择"
+                />
+                {/* <Form.Item  >
+                                    <DiyFormCascader data={getSelectorData('IndicatorType', 'Cascader')} />
+                            </Form.Item> */}
+
+                <ProFormCascader
+                  colProps={{ md: 12, xl: 8 }}
+                  name="indicatorTypeList"
+                  label="指标类别"
+                  fieldProps={{
+                    size: 'small',
+                    maxTagCount: 'responsive',
+                    multiple: true,
+                    showCheckedStrategy: 'SHOW_CHILD',
+                    fieldNames: { label: 'name', value: 'code', children: 'children' },
+                    options: getSelectorData('IndicatorType', 'Cascader'),
+                    onChange(value, selectOptions) {
+                      console.log({ value, selectOptions });
+
+                      var nodes: any[] = [];
+
+                      function parseTreeJson(array: any[]) {
+                        if (!array) return [];
+                        for (let index = 0; index < array.length; index++) {
+                          const element = array[index];
+                          // 判断element.children是对象
+                          if (element.children && typeof element.children == 'object') {
+                            parseTreeJson(element.children);
+                          } else {
+                            // 判断是否为子节点
+                            if (!(element.children || typeof element.children == 'object')) {
+                              // 获得符合的 node
+                              nodes.push(element);
+                            }
+                          }
+                        }
+                      }
+
+                      if (value.length > 0) {
+                        for (let index = 0; index < value.length; index++) {
+                          let current = value[index];
+                          let key = current[current.length - 1];
+                          const result = selectOptions[index].filter((t: any) => t.code == key);
+
+                          if (result[0].children) {
+                            //非直接叶子节点
+                            parseTreeJson(result[0].children);
+                          }
+                          if (!result[0].children) {
+                            //直接叶子节点
+                            nodes.push({ code: result[0].code });
+                          }
+                        }
+                      }
+
+                      const codes = nodes.map((t) => t.code);
+                      manaInfoformRef.current?.setFieldsValue({ _indicatorTypeList: codes });
+                    },
+                  }}
+                />
+                <ProFormText label="管理目标值" colProps={{ md: 12, xl: 8 }} name="targetValue" />
+
+                <ProFormSelect
+                  colProps={{ md: 12, xl: 8 }}
+                  name="indicatorDepartmentList"
+                  label="职能科室归属"
+                  fieldProps={{
+                    mode: 'multiple',
+                    size: 'small',
+                    maxTagCount: 'responsive',
+                  }}
+                  options={getSelectorData('IndicatorDepartment') as DefaultOptionType[]}
+                  placeholder="请选择"
+                />
+
+                <ProFormDigit colProps={{ md: 12, xl: 8 }} name="reference" label="同级参考值" placeholder="请输入" />
+                {/* <ProFormText colProps={{ md: 12, xl: 8 }} name="referenceDesc" label="参考值来源说明" placeholder="请输入" /> */}
+                <ProFormTextArea
+                  colProps={{ md: 12, xl: 8 }}
+                  name="referenceDesc"
+                  label="参考值来源说明"
+                  placeholder="请输入"
+                  fieldProps={{
+                    allowClear: true,
+                    autoSize: true,
+                    size: 'small',
+                  }}
+                />
+
+                <ProFormSelect
+                  colProps={{ md: 12, xl: 8 }}
+                  name="indicatorCommitteeList"
+                  label="委员会归属"
+                  fieldProps={{
+                    size: 'small',
+                    mode: 'multiple',
+                    maxTagCount: 'responsive',
+                  }}
+                  options={getSelectorData('IndicatorCommittee') as DefaultOptionType[]}
+                  placeholder="请选择"
+                />
+
+                <ProFormCascader
+                  colProps={{ md: 12, xl: 8 }}
+                  name="indicatorExternalList"
+                  label="外部指标"
+                  fieldProps={{
+                    size: 'small',
+                    multiple: true,
+                    maxTagCount: 'responsive',
+                    fieldNames: { label: 'name', value: 'code', children: 'children' },
+                    options: getSelectorData('IndicatorExternal', 'Cascader'),
+                    onChange(value, selectOptions) {
+                      // console.log({ value, selectOptions });
+
+                      var nodes: any[] = [];
+
+                      function parseTreeJson(array: any[]) {
+                        if (!array) return [];
+                        for (let index = 0; index < array.length; index++) {
+                          const element = array[index];
+                          // 判断element.children是对象
+                          if (element.children && typeof element.children == 'object') {
+                            parseTreeJson(element.children);
+                          } else {
+                            // 判断是否为子节点
+                            if (!(element.children || typeof element.children == 'object')) {
+                              // 获得符合的 node
+                              nodes.push(element);
+                            }
+                          }
+                        }
+                      }
+
+                      if (value.length > 0) {
+                        for (let index = 0; index < value.length; index++) {
+                          let current = value[index];
+                          let key = current[current.length - 1];
+                          const result = selectOptions[index].filter((t: any) => t.code == key);
+                          console.log({ result });
+                          if (result[0].children) {
+                            //非直接叶子节点
+                            parseTreeJson(result[0].children);
+                          }
+                          if (!result[0].children) {
+                            //直接叶子节点
+                            nodes.push({ code: result[0].code });
+                          }
+                        }
+                      }
+
+                      const codes = nodes.map((t) => t.code);
+                      manaInfoformRef.current?.setFieldsValue({ _indicatorExternalList: codes });
+                    },
+                  }}
+                />
+                {/* <ProFormText colProps={{ md: 12, xl: 8 }} name="remark" label="其他备注" placeholder="请输入" /> */}
+                <ProFormTextArea
+                  colProps={{ md: 12, xl: 8 }}
+                  name="remark"
+                  label="其他备注"
+                  placeholder="请输入"
+                  fieldProps={{
+                    allowClear: true,
+                    autoSize: true,
+                    size: 'small',
+                  }}
+                />
+              </ProForm>
+            </div>
+          </div>
+
+          <div className="card">
+            <div className="cardTitle">指标展示设定</div>
+            <div className="formContent">
+              <ProForm
+                formRef={showSetInfoformRef}
+                layout={'vertical'}
+                grid={true}
+                autoFocusFirstInput={false}
+                rowProps={{
+                  gutter: [16, 0],
+                }}
+                initialValues={
+                  renderType == 'ADD'
+                    ? {}
+                    : renderType == 'EDIT' && record
+                    ? {
+                        ...record,
+                        caseBreakdown: record.caseBreakdown ? record.caseBreakdown.split('/') : [],
+                        chartType: record.chartType ? record.chartType.split('/') : [],
+                        dataDownload: record.dataDownload ? record.dataDownload.split('/') : [],
+                        dataSum: record.dataSum ? record.dataSum.split('/') : [],
+                        drillLevel: record.drillLevel ? record.drillLevel.split('/') : [],
+                      }
+                    : {}
+                }
+                submitter={{
+                  render: () => null,
+                }}
+              >
+                <ProFormSelect
+                  colProps={{ md: 12, xl: 8 }}
+                  name="appearDate"
+                  label="呈现期间"
+                  fieldProps={{ size: 'small' }}
+                  options={[
+                    {
+                      label: '年',
+                      value: '年',
+                    },
+                    {
+                      label: '季',
+                      value: '季',
+                    },
+                    {
+                      label: '月',
+                      value: '月',
+                    },
+                  ]}
+                  placeholder="请选择"
+                />
+                <ProFormSelect
+                  colProps={{ md: 12, xl: 8 }}
+                  name="chartType"
+                  label="图表形态"
+                  fieldProps={{
+                    size: 'small',
+                    mode: 'multiple',
+                    maxTagCount: 'responsive',
+                  }}
+                  options={[
+                    {
+                      label: '统计表',
+                      value: '统计表',
+                    },
+                    {
+                      label: '直方图',
+                      value: '直方图',
+                    },
+                    {
+                      label: '趋势图',
+                      value: '趋势图',
+                    },
+                    {
+                      label: '其他',
+                      value: '其他',
+                    },
+                  ]}
+                  placeholder="请选择"
+                />
+                <ProFormSelect
+                  colProps={{ md: 12, xl: 8 }}
+                  name="dataSum"
+                  label="数据汇总呈现"
+                  fieldProps={{
+                    size: 'small',
+                    mode: 'multiple',
+                    maxTagCount: 'responsive',
+                  }}
+                  options={[
+                    { label: '全院', value: '全院' },
+                    { label: '分院', value: '分院' },
+                  ]}
+                  placeholder="请选择"
+                />
+
+                <ProFormSelect
+                  colProps={{ md: 12, xl: 8 }}
+                  name="drillLevel"
+                  label="资料下钻层级"
+                  fieldProps={{
+                    size: 'small',
+                    mode: 'multiple',
+                    maxTagCount: 'responsive',
+                  }}
+                  options={[
+                    {
+                      label: '全院',
+                      value: '全院',
+                    },
+                    {
+                      label: '分院区',
+                      value: '分院区',
+                    },
+                    {
+                      label: '科别',
+                      value: '科别',
+                    },
+                    {
+                      label: '医师别',
+                      value: '医师别',
+                    },
+                    {
+                      label: '个案',
+                      value: '个案',
+                    },
+                  ]}
+                  placeholder="请选择"
+                />
+                <ProFormSelect
+                  colProps={{ md: 12, xl: 8 }}
+                  name="dataDownload"
+                  label="资料下载"
+                  fieldProps={{
+                    size: 'small',
+                    mode: 'multiple',
+                    maxTagCount: 'responsive',
+                  }}
+                  options={[
+                    {
+                      label: '全院',
+                      value: '全院',
+                    },
+                    {
+                      label: '分院区',
+                      value: '分院区',
+                    },
+                    {
+                      label: '科别',
+                      value: '科别',
+                    },
+                    {
+                      label: '医师别',
+                      value: '医师别',
+                    },
+                    {
+                      label: '个案数',
+                      value: '个案数',
+                    },
+                  ]}
+                  placeholder="请选择"
+                />
+                <ProFormSelect
+                  colProps={{ md: 12, xl: 8 }}
+                  name="caseBreakdown"
+                  label="个案明细字段"
+                  fieldProps={{
+                    size: 'small',
+                    mode: 'multiple',
+                    maxTagCount: 'responsive',
+                  }}
+                  options={[
+                    {
+                      label: '看诊科别',
+                      value: '看诊科别',
+                    },
+                    {
+                      label: '就诊日期',
+                      value: '就诊日期',
+                    },
+                    {
+                      label: '住院科别',
+                      value: '住院科别',
+                    },
+                    {
+                      label: '出院科别',
+                      value: '出院科别',
+                    },
+                    {
+                      label: '病历号',
+                      value: '病历号',
+                    },
+                    {
+                      label: '住院日期时间',
+                      value: '住院日期时间',
+                    },
+                    {
+                      label: '转出日期',
+                      value: '转出日期',
+                    },
+                    {
+                      label: '出院日期时间',
+                      value: '出院日期时间',
+                    },
+                    {
+                      label: '出院类别',
+                      value: '出院类别',
+                    },
+                    {
+                      label: '出院诊断码',
+                      value: '出院诊断码',
+                    },
+                    {
+                      label: '合并症码',
+                      value: '合并症码',
+                    },
+                    {
+                      label: '死亡原因',
+                      value: '死亡原因',
+                    },
+                    {
+                      label: '手术代码',
+                      value: '手术代码',
+                    },
+                    {
+                      label: '其他',
+                      value: '其他',
+                    },
+                  ]}
+                  placeholder="请选择"
+                />
+              </ProForm>
+            </div>
+          </div>
+
+          <div className="card">
+            <div className="cardTitle">管理员维护信息</div>
+            <div className="formContent">
+              <ProForm
+                formRef={adminInfoformRef}
+                layout={'vertical'}
+                grid={true}
+                autoFocusFirstInput={false}
+                initialValues={
+                  renderType == 'ADD'
+                    ? { isPublic: 1 }
+                    : renderType == 'EDIT' && record
+                    ? {
+                        ...record,
+                        indicatorMenuList: setFormInit('indicatorMenuList'),
+                      }
+                    : {}
+                }
+                rowProps={{
+                  gutter: [16, 0],
+                }}
+                submitter={{
+                  render: () => null,
+                }}
+              >
+                <ProFormCascader
+                  colProps={{ md: 12, xl: 8 }}
+                  name="indicatorMenuList"
+                  label="指标目录"
+                  rules={[{ required: true, message: '这是必填项' }]}
+                  fieldProps={{
+                    size: 'small',
+                    multiple: true,
+                    fieldNames: { label: 'name', value: 'code' },
+                    onChange(value: any, selectOptions: any) {
+                      console.log({ value, selectOptions });
+
+                      var nodes: any[] = [];
+
+                      function parseTreeJson(array: any[]) {
+                        if (!array) return [];
+                        for (let index = 0; index < array.length; index++) {
+                          const element = array[index];
+                          // 判断element.children是对象
+                          if (element.children && typeof element.children == 'object') {
+                            parseTreeJson(element.children);
+                          } else {
+                            // 判断是否为子节点
+                            if (!(element.children || typeof element.children == 'object')) {
+                              // 获得符合的 node
+                              nodes.push(element);
+                            }
+                          }
+                        }
+                      }
+
+                      if (value.length > 0) {
+                        for (let index = 0; index < value.length; index++) {
+                          let current = value[index];
+                          let key = current[current.length - 1];
+                          const result = selectOptions[index].filter((t: any) => t.code == key);
+                          // console.log({result});
+                          if (result[0].children) {
+                            //非直接叶子节点
+                            parseTreeJson(result[0].children);
+                          }
+                          if (!result[0].children) {
+                            //直接叶子节点
+                            nodes.push({ code: result[0].code });
+                          }
+                        }
+                      }
+
+                      const codes = nodes.map((t) => t.code);
+                      console.log({ codes, nodes });
+                      adminInfoformRef.current?.setFieldsValue({ _indicatorMenuList: codes });
+                    },
+                  }}
+                  request={async () => {
+                    if (location.search.length > 0) {
+                      const id = location.search.split('=')[1];
+                      if (id) {
+                        const dirData = await getIndicatorDictionary();
+                        if (dirData) {
+                          const result = dirData.filter((t) => t.code == id);
+
+                          if (result.length > 0) {
+                            return result[0].children;
+                          }
+                        }
+                        return [];
+                      }
+                    }
+
+                    return [];
+                  }}
+                  placeholder="请选择"
+                />
+
+                <ProFormSelect
+                  colProps={{ md: 12, xl: 8 }}
+                  name="isPublic"
+                  label="是否公开"
+                  fieldProps={{ size: 'small' }}
+                  options={[
+                    {
+                      label: '是',
+                      value: 1,
+                    },
+                    {
+                      label: '否',
+                      value: 0,
+                    },
+                  ]}
+                  placeholder="请选择"
+                />
+                <ProFormSelect
+                  colProps={{ md: 12, xl: 8 }}
+                  name="version"
+                  label="版本"
+                  fieldProps={{ size: 'small' }}
+                  options={[
+                    {
+                      label: '版本1',
+                      value: 1,
+                    },
+                    {
+                      label: '版本2',
+                      value: 2,
+                    },
+                    {
+                      label: '版本3',
+                      value: 3,
+                    },
+                  ]}
+                  placeholder="请选择"
+                />
+              </ProForm>
+            </div>
+          </div>
+        </div>
+      </Drawer>
+    </div>
+  );
+};
+
+export default DrawerForm;

+ 70 - 0
src/pages/platform/setting/indicatoLagacy/ DrawerForm/style.less

@@ -0,0 +1,70 @@
+.DrawerForm {
+  .drawerHeader {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    align-items: center;
+    padding: 16px;
+
+    .drawerTitle {
+      height: 16px;
+      line-height: 16px;
+      font-weight: bold;
+      font-size: 16px;
+      color: #17181a;
+    }
+
+    .btnGroup {
+      & > span {
+        display: inline-block;
+        width: 56px;
+        height: 24px;
+        cursor: pointer;
+        text-align: center;
+        line-height: 24px;
+        background: #fafcff;
+        border-radius: 4px;
+        border: 1px solid #dae2f2;
+        font-weight: 400;
+        font-size: 14px;
+        color: #17181a;
+        margin-right: 8px;
+
+        &.primary {
+          background: #3377ff;
+          border: 1px solid transparent;
+          color: #fff;
+        }
+
+        &:last-child {
+          margin-right: 0;
+        }
+      }
+    }
+  }
+
+  .drawerContent {
+    height: calc(100vh - 55px);
+    overflow-y: scroll;
+    .card {
+      padding: 16px;
+      margin: 16px;
+      background: #ffffff;
+      border-radius: 4px;
+
+      .cardTitle {
+        height: 16px;
+        font-size: 16px;
+        font-family: SourceHanSansCN-Medium, SourceHanSansCN;
+        font-weight: 500;
+        color: #17181a;
+        line-height: 16px;
+        margin-bottom: 24px;
+      }
+
+      &:first-child {
+        margin-top: 0;
+      }
+    }
+  }
+}

+ 86 - 0
src/pages/platform/setting/indicatoLagacy/TreeDirectory/index.tsx

@@ -0,0 +1,86 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2022-07-16 14:33:12
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-03-07 13:36:11
+ * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/indicatorMana/TreeDirectory/index.tsx
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+
+import { Divider, Tree } from 'antd';
+import { DataNode, DirectoryTreeProps } from 'antd/es/tree';
+import React, { Key, useEffect, useState } from 'react';
+
+import './style.less';
+
+const { DirectoryTree } = Tree;
+
+const TreeDirectory = ({ data, onSelectChange }: { data: any[]; onSelectChange?: (selectedNode: any) => void }) => {
+  const [defaultExpandedKeys, set_defaultExpandedKeys] = useState<Key[]>([]);
+  const [defaultSelectedKeys, set_defaultSelectedKeys] = useState<string[]>([]);
+
+  const onSelect: DirectoryTreeProps['onSelect'] = (keys, info) => {
+    // console.log('Trigger Select', keys, info);
+    onSelectChange && onSelectChange(info.node);
+  };
+
+  const onExpand: DirectoryTreeProps['onExpand'] = (keys, info) => {
+    // console.log('Trigger Expand', keys, info);
+    set_defaultExpandedKeys(keys);
+  };
+
+  useEffect(() => {
+    const getName: any = (obj: any) => {
+      let isArr = Array.isArray(obj);
+      let reslut = [];
+      if (isArr) {
+        obj.forEach((item: any) => {
+          reslut.push(...getName(item));
+        });
+      } else {
+        reslut.push(obj.code);
+        if (obj.children) {
+          reslut.push(...getName(obj.children));
+        }
+      }
+      return reslut;
+    };
+
+    if (data.length > 0) {
+      const keys = getName(data);
+      set_defaultExpandedKeys(keys);
+      set_defaultSelectedKeys([data[0].children[0].code]);
+      onSelectChange && onSelectChange({ code: data[0].children[0].code });
+    }
+  }, [data]);
+
+  useEffect(() => {}, [defaultExpandedKeys]);
+
+  return (
+    <div className="TreeDirectory">
+      {data.length > 0 && defaultExpandedKeys.length > 0 && (
+        <DirectoryTree
+          // multiple
+          blockNode
+          height={750}
+          rootStyle={{ overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis', width: '100%' }}
+          fieldNames={{ title: 'name', key: 'code' }}
+          expandedKeys={defaultExpandedKeys}
+          // defaultExpandedKeys={defaultExpandedKeys}
+          defaultSelectedKeys={defaultSelectedKeys}
+          // expandedKeys={defaultExpandedKeys}
+          onSelect={onSelect}
+          onExpand={onExpand}
+          treeData={data}
+          // titleRender={(nodeData) => {
+          //     return (
+          //         <span style={{ display: 'inline-block', overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis', width: '70%',height:30 }}>{nodeData.name}</span>
+          //     )
+          // }}
+        />
+      )}
+    </div>
+  );
+};
+
+export default TreeDirectory;

+ 0 - 0
src/pages/platform/setting/indicatoLagacy/TreeDirectory/style.less


+ 469 - 0
src/pages/platform/setting/indicatoLagacy/index.tsx

@@ -0,0 +1,469 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2022-07-12 11:14:21
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-03-07 16:00:03
+ * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/indicatorMana/index.tsx
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+
+import { ActionType, ProColumns } from '@ant-design/pro-table';
+import React, { useEffect, useRef, useState } from 'react';
+import { Button, Divider, Dropdown, Popconfirm } from 'antd';
+
+import { DownOutlined } from '@ant-design/icons';
+import { addIndicatorManaList, delIndicatorManaList, editIndicatorManaList, getIndicatorCateList_old, getIndicatorManaList_old, IndicatorManaItemType } from '@/service/indicator';
+import TreeDirectory from './TreeDirectory';
+
+import './style.less';
+import { getIndicatorDictionary, IndicatorDictionaryDataType } from '@/service/dictionary';
+import { useLocation } from 'umi';
+import { KCIMLeftList } from '../../components/KCIMLeftList';
+import KCTable from '@/components/kcTable';
+import { KCInput } from '@/components/KCInput';
+import DrawerForm from './ DrawerForm/drawer';
+
+const IndicatorMana = () => {
+  const [reloadTable, set_reloadTable] = useState(false);
+  const [drawerVisible, set_drawerVisible] = useState(false);
+  const [indicateType, set_indicateType] = useState<IndicatorDictionaryDataType[]>([]);
+  const [currentEditRowData, set_currentEditRowData] = useState<any | undefined>(undefined);
+  const [indicatorCateTreeData, set_indicatorCateTreeData] = useState<any[]>([]);
+  const [defaultExpandedRowKeys, set_defaultExpandedRowKeys] = useState<string[]>([]);
+  const [tableData, set_tableData] = useState<any[]>([]);
+  const [cateId, set_cateId] = useState<string>();
+
+  const [tableDataFilterParams, set_tableDataFilterParams] = useState<any | undefined>();
+  const [tableDataSearchKeywords, set_tableDataSearchKeywords] = useState<string>('');
+  const [currentSelectedLeft, set_currentSelectedLeft] = useState<any>(undefined);
+
+  const [drawerActype, set_drawerActype] = useState<'ADD' | 'EDIT'>('ADD');
+
+  const indicatorTableRef = useRef<ActionType>();
+
+  const columns: ProColumns<any>[] = [
+    {
+      title: '指标名称',
+      dataIndex: 'name',
+      hideInSearch: false,
+      ellipsis: true,
+    },
+    // {
+    //   title: '指标编码',
+    //   dataIndex: 'code',
+    //   ellipsis: true,
+    //   render: (text, record) => {
+    //     if (record) {
+    //       const { indicatorBoolean, code } = record;
+    //       return indicatorBoolean ? code : ''
+    //     } else {
+    //       return ''
+    //     }
+
+    //   }
+    // },
+    {
+      title: '指标类别',
+      dataIndex: 'indicatorType',
+      hideInSearch: true,
+      ellipsis: true,
+      hideInTable: true,
+      valueType: 'select',
+      request: async () => {
+        const resp = await getIndicatorDictionary();
+        if (resp) {
+          const data = resp.filter((t) => t.code == 'IndicatorType');
+          if (data.length > 0) {
+            return data[0].children.map((t) => ({ label: t.name, value: t.code }));
+          }
+        }
+        return [];
+      },
+      render: (text, record) => {
+        if (record) {
+          const { indicatorBoolean, indicatorTypeList } = record;
+          return indicatorBoolean && indicatorTypeList ? indicatorTypeList.map((t: any) => t.name).join('/') : '';
+        } else {
+          return '';
+        }
+      },
+    },
+    {
+      title: '外部指标',
+      dataIndex: 'outsideIndexes',
+      hideInSearch: true,
+      ellipsis: true,
+      hideInTable: true,
+      valueType: 'cascader',
+      fieldProps: {
+        fieldNames: { label: 'name', value: 'code' },
+        placeholder: '请选择',
+      },
+
+      request: async () => {
+        const resp = await getIndicatorDictionary();
+        if (resp) {
+          const data = resp.filter((t) => t.code == 'IndicatorExternal');
+          if (data.length > 0) {
+            return data[0].children;
+          }
+        }
+        return [];
+      },
+      render: (text, record) => {
+        if (record) {
+          const { indicatorBoolean, indicatorExternalList } = record;
+          return indicatorBoolean ? (indicatorExternalList ? indicatorExternalList.map((t: any) => t.name).join('/') : '-') : '';
+        } else {
+          return '';
+        }
+      },
+    },
+    {
+      title: '指标编码',
+      dataIndex: 'code',
+      width: 80,
+      hideInSearch: true,
+      ellipsis: true,
+    },
+    {
+      title: '指标定义',
+      dataIndex: 'targetDefinition',
+      hideInSearch: true,
+      ellipsis: true,
+    },
+
+    {
+      title: '操作',
+      width: 90,
+      key: 'option',
+      valueType: 'option',
+      render: (text, record) => {
+        const items = [
+          {
+            key: '1',
+            label: (
+              <a key="linka" href={record.indicatorPath} target="_blank">
+                数据展示
+              </a>
+            ),
+          },
+          {
+            key: '3',
+            label: (
+              <Popconfirm title="是否确定删除?" onConfirm={() => delHandle(record)} okText="确定" cancelText="取消" key="link2">
+                <a>删除</a>
+              </Popconfirm>
+            ),
+          },
+        ];
+
+        if (record && record.indicatorBoolean) {
+          return [
+            <a key="link3" onClick={() => actionHandle('EDIT', record)}>
+              管理
+            </a>,
+            <Dropdown menu={{ items }}>
+              <a>
+                <DownOutlined />
+              </a>
+            </Dropdown>,
+          ];
+        }
+
+        return [];
+      },
+    },
+  ];
+
+  const getIndicatorDir = async () => {
+    const resp = await getIndicatorDictionary();
+    if (resp) {
+      set_indicateType(resp);
+    }
+  };
+
+  const getData = async (params: any) => {
+    const { current = 1, name, indicatorType, outsideIndexes } = params;
+    if (cateId) {
+      const resp = await getIndicatorManaList_old({
+        current: 1,
+        pageSize: 0,
+        menuCode: cateId as string,
+        indicatorName: name,
+        indicatorType: indicatorType,
+        outsideIndexes: outsideIndexes && outsideIndexes.length > 0 ? outsideIndexes[outsideIndexes.length - 1] : '',
+      });
+
+      if (resp && resp[0] && resp[0].children) {
+        const getName: any = (obj: any) => {
+          let isArr = Array.isArray(obj);
+          let reslut = [];
+          if (isArr) {
+            obj.forEach((item: any) => {
+              reslut.push(...getName(item));
+            });
+          } else {
+            reslut.push(obj.menuId);
+
+            if (obj.children) {
+              reslut.push(...getName(obj.children));
+            }
+          }
+          return reslut;
+        };
+
+        set_tableData(resp[0].children as unknown as []);
+
+        set_defaultExpandedRowKeys(getName(resp[0].children));
+
+        return {
+          data: resp[0].children as unknown as [],
+          success: true,
+          total: 1,
+        };
+      } else {
+        return {
+          data: [],
+          success: true,
+          total: 0,
+        };
+      }
+    }
+
+    return {
+      data: [],
+      success: false,
+      total: 0,
+    };
+  };
+
+  const getIndicatorCateTree = async () => {
+    // console.log(location.search);
+    let menuId = '';
+    if (location.search.length > 0) {
+      const searchArr = location.search.split('=');
+      // console.log({ searchArr });
+      if (searchArr[1]) {
+        const resp = await getIndicatorCateList_old({ menuCode: searchArr[1] });
+        if (resp) {
+          set_indicatorCateTreeData(resp);
+        }
+      }
+    }
+  };
+
+  const actionHandle = async (key: 'EDIT' | 'DEL', record: IndicatorDictionaryDataType) => {
+    set_currentEditRowData(record);
+    if (key == 'EDIT') {
+      set_drawerActype('EDIT');
+      set_drawerVisible(true);
+    }
+  };
+
+  const addHandle = () => {
+    set_drawerActype('ADD');
+    set_currentEditRowData(undefined);
+    set_drawerVisible(true);
+  };
+
+  const delHandle = async (record: IndicatorDictionaryDataType) => {
+    const resp = await delIndicatorManaList({ id: record.id });
+    if (resp) {
+      indicatorTableRef.current?.reload();
+    }
+  };
+
+  const onVisibleChangeHandle = (bool: boolean) => {
+    if (!bool) {
+      set_drawerVisible(false);
+    }
+    if (bool) {
+      set_drawerVisible(true);
+    }
+  };
+
+  const drawerFormCommitHanndle = async (data: any) => {
+    // const formData = Object.assign({}, data[0].baseInfoformRef, data[1].manaInfoformRef, data[2].showSetInfoformRef, data[3].adminInfoformRef);
+    //const _formData = {...data[0].baseInfoformRef,...data[1].manaInfoformRef,...data[2].showSetInfoformRef,...data[3].adminInfoformRef}
+
+    let formData: any;
+    data.forEach((element: any) => {
+      if (element.baseInfoformRef) {
+        formData = { ...formData, ...element.baseInfoformRef };
+      }
+      if (element.manaInfoformRef) {
+        formData = { ...formData, ...element.manaInfoformRef };
+      }
+      if (element.showSetInfoformRef) {
+        formData = { ...formData, ...element.showSetInfoformRef };
+      }
+      if (element.adminInfoformRef) {
+        formData = { ...formData, ...element.adminInfoformRef };
+      }
+    });
+
+    if (drawerActype == 'ADD') {
+      const resp = await addIndicatorManaList({
+        ...formData,
+        caseBreakdown: formData.caseBreakdown ? formData.caseBreakdown.join('/') : '',
+        dataDownload: formData.dataDownload ? formData.dataDownload.join('/') : '',
+        drillLevel: formData.drillLevel ? formData.drillLevel.join('/') : '',
+        dataSum: formData.dataSum ? formData.dataSum.join('/') : '',
+        chartType: formData.chartType ? formData.chartType.join('/') : '',
+
+        indicatorMenuList: formData.indicatorMenuList ? formData.indicatorMenuList : [],
+        indicatorTypeList: formData.indicatorTypeList ? formData.indicatorTypeList : [],
+        indicatorExternalList: formData.indicatorExternalList ? formData.indicatorExternalList : [],
+        divisionId: `${formData.divisionId}`,
+      });
+
+      if (resp) {
+        set_drawerVisible(false);
+        indicatorTableRef.current?.reload();
+      }
+    }
+
+    if (drawerActype == 'EDIT') {
+      const resp = await editIndicatorManaList({
+        ...currentEditRowData,
+        ...formData,
+        caseBreakdown: formData.caseBreakdown ? formData.caseBreakdown.join('/') : '',
+        dataDownload: formData.dataDownload ? formData.dataDownload.join('/') : '',
+        drillLevel: formData.drillLevel ? formData.drillLevel.join('/') : '',
+        dataSum: formData.dataSum ? formData.dataSum.join('/') : '',
+        chartType: formData.chartType ? formData.chartType.join('/') : '',
+      });
+      if (resp) {
+        set_drawerVisible(false);
+        indicatorTableRef.current?.reload();
+      }
+    }
+  };
+
+  const onSelectChangehandle = (currentSelected: any) => {
+    set_currentSelectedLeft(currentSelected);
+    currentSelected && set_cateId(currentSelected.code);
+  };
+
+  const tableDataSearchHandle = (paramName: string) => {
+    set_tableDataFilterParams({
+      ...tableDataFilterParams,
+      current: 1,
+      [`${paramName}`]: tableDataSearchKeywords,
+    });
+  };
+
+  useEffect(() => {
+    if (tableData) {
+      const keys = tableData.map((t: any) => (t ? t.code : ''));
+    }
+  }, [tableData]);
+
+  useEffect(() => {
+    console.log({ cateId });
+    indicatorTableRef.current?.reload();
+  }, [cateId]);
+
+  useEffect(() => {
+    getIndicatorCateTree();
+  }, [location.search]);
+
+  useEffect(() => {
+    getIndicatorDir();
+    getIndicatorCateTree();
+  }, []);
+
+  return (
+    <div className="IndicatorMana">
+      <DrawerForm actType={drawerActype} visible={drawerVisible} onVisibleChange={onVisibleChangeHandle} onFinish={(data) => drawerFormCommitHanndle(data)} record={currentEditRowData} />
+      <div className="content">
+        <div className="left">
+          {/* <TreeDirectory data={indicatorCateTreeData} onSelectChange={(info) => onSelectChangehandle(info)} /> */}
+          <KCIMLeftList
+            searchKey={'name'}
+            listType="tree"
+            dataSource={indicatorCateTreeData}
+            fieldNames={{ title: 'name', key: 'code' }}
+            contentH={'100%'}
+            onChange={(info) => onSelectChangehandle(info)}
+          />
+        </div>
+        <div className="right">
+          <div className="tableTitle">{currentSelectedLeft && currentSelectedLeft.name}</div>
+          <div className="toolBar">
+            <div className="filter">
+              <div className="filterItem">
+                <KCInput
+                  placeholder={'指标名称'}
+                  style={{ width: 240 }}
+                  search
+                  allowClear
+                  onChange={(e) => {
+                    set_tableDataSearchKeywords(e.target.value);
+                    if (e.target.value.length == 0) {
+                      set_tableDataFilterParams({
+                        ...tableDataFilterParams,
+                        current: 1,
+                        name: '',
+                      });
+                    }
+                  }}
+                  onSearch={() => tableDataSearchHandle('name')}
+                />
+              </div>
+            </div>
+            <div className="btnGroup">
+              <span className="add" onClick={addHandle}>
+                新增
+              </span>
+            </div>
+          </div>
+          {cateId && (
+            <KCTable
+              newVer
+              rowKey="menuId"
+              columns={columns}
+              params={tableDataFilterParams}
+              actionRef={indicatorTableRef}
+              scroll={{ y: `calc(100vh - 228px)` }}
+              expandable={{
+                expandedRowKeys: defaultExpandedRowKeys,
+                onExpand: (expanded, record) => {
+                  // console.log({expanded, record});
+                  let _expandedKeys = [...defaultExpandedRowKeys];
+                  if (expanded) {
+                    _expandedKeys.push(record.menuId);
+                  } else {
+                    const index = _expandedKeys.findIndex((t) => t == `${record.menuId}`);
+                    if (index != -1) {
+                      _expandedKeys.splice(index, 1);
+                    }
+                  }
+                  set_defaultExpandedRowKeys([..._expandedKeys]);
+                },
+              }}
+              reload={reloadTable}
+              pagination={false}
+              // toolBarRender={() => [
+              //   // <Button key="1" onClick={addHandle}>
+              //   //   上传
+              //   // </Button>,
+              //   // <Button key="2" onClick={addHandle}>
+              //   //   下载
+              //   // </Button>,
+              //   <Button key="3" type="primary" onClick={addHandle}>
+              //     新增
+              //   </Button>,
+              // ]}
+              options={false}
+              request={(params) => getData(params)}
+            />
+          )}
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default IndicatorMana;

+ 74 - 0
src/pages/platform/setting/indicatoLagacy/style.less

@@ -0,0 +1,74 @@
+.IndicatorMana {
+  width: 100%;
+
+  .content {
+    display: flex;
+    flex: 1;
+    width: 100%;
+    height: 100%;
+    flex-direction: row;
+    justify-content: space-between;
+    align-items: flex-start;
+
+    .left {
+      width: 220px;
+      height: calc(100vh - 80px);
+      padding: 0px;
+      padding-top: 8px;
+      border-radius: 4px;
+      background-color: #fff;
+    }
+
+    .right {
+      width: calc(100% - 236px);
+      padding: 16px;
+      background-color: #fff;
+      border-radius: 4px;
+
+      .tableTitle {
+        height: 16px;
+        font-weight: bold;
+        font-size: 16px;
+        color: #17181a;
+        line-height: 16px;
+        margin-bottom: 16px;
+      }
+
+      .toolBar {
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 16px;
+
+        .filter {
+          display: flex;
+          flex-direction: row;
+          justify-content: flex-start;
+          align-items: center;
+
+          .filterItem {
+            display: flex;
+            flex-direction: row;
+            justify-content: center;
+            align-items: center;
+          }
+        }
+
+        .btnGroup {
+          .add {
+            cursor: pointer;
+            display: inline-block;
+            font-size: 14px;
+            font-weight: 400;
+            color: #ffffff;
+            line-height: 24px;
+            padding: 0 14px;
+            background: #3377ff;
+            border-radius: 4px;
+          }
+        }
+      }
+    }
+  }
+}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 674 - 483
src/pages/platform/setting/indicatorMana/DrawerForm/drawer.tsx


+ 27 - 14
src/pages/platform/setting/indicatorMana/DrawerForm/style.less

@@ -1,3 +1,14 @@
+.DATAMANA_Transfer {
+  .kcmp-ant-transfer-list {
+    overflow: hidden;
+    border-radius: 8px !important;
+    border: 1px solid #dae2f2 !important;
+    &:nth-child(3) {
+      display: none !important;
+    }
+  }
+}
+
 .DrawerForm {
   .drawerHeader {
     display: flex;
@@ -6,33 +17,32 @@
     align-items: center;
     padding: 16px;
 
-
     .drawerTitle {
       height: 16px;
       line-height: 16px;
       font-weight: bold;
       font-size: 16px;
-      color: #17181A;
+      color: #17181a;
     }
 
     .btnGroup {
-      &>span {
+      & > span {
         display: inline-block;
         width: 56px;
         height: 24px;
         cursor: pointer;
         text-align: center;
         line-height: 24px;
-        background: #FAFCFF;
+        background: #fafcff;
         border-radius: 4px;
-        border: 1px solid #DAE2F2;
+        border: 1px solid #dae2f2;
         font-weight: 400;
         font-size: 14px;
-        color: #17181A;
+        color: #17181a;
         margin-right: 8px;
 
         &.primary {
-          background: #3377FF;
+          background: #3377ff;
           border: 1px solid transparent;
           color: #fff;
         }
@@ -41,34 +51,37 @@
           margin-right: 0;
         }
       }
-
     }
   }
 
+  .DATAMANA_content {
+    background: #ffffff;
+    border-radius: 8px;
+    // border: 1px solid #dae2f2;
+    margin: 0 16px;
+  }
   .drawerContent {
     height: calc(100vh - 55px);
     overflow-y: scroll;
     .card {
       padding: 16px;
       margin: 16px;
-      background: #FFFFFF;
+      background: #ffffff;
       border-radius: 4px;
-  
+
       .cardTitle {
         height: 16px;
         font-size: 16px;
         font-family: SourceHanSansCN-Medium, SourceHanSansCN;
         font-weight: 500;
-        color: #17181A;
+        color: #17181a;
         line-height: 16px;
         margin-bottom: 24px;
       }
 
       &:first-child {
-          margin-top: 0;
+        margin-top: 0;
       }
     }
   }
-
-  
 }

+ 63 - 0
src/pages/platform/setting/indicatorMana/authHisttory/index.tsx

@@ -0,0 +1,63 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2024-12-06 14:42:57
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-02-18 14:36:58
+ * @FilePath: /MediResourceManaSys/src/pages/qualificationMana/qualificationAuth/authHisttory/index.tsx
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AEi m z z
+ */
+
+import { createFromIconfontCN } from '@ant-design/icons';
+import React, { Children } from 'react';
+import { Timeline } from 'antd';
+import './style.less';
+
+const IconFont = createFromIconfontCN({
+  scriptUrl: '',
+});
+
+export default function AuthHisContent({ hisList }: { hisList: any[] }) {
+  const getPeriodStr = (code: number) => {
+    // const result = authTimeType.filter((a)=>a.value == code);
+    // if(result.length>0){
+    //     return result[0].label
+    // }
+    return undefined;
+  };
+
+  const dotNode = (isActive = false) => {
+    return (
+      <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', width: 16, height: 16, border: '2px solid #E7EBF2', borderRadius: '50%' }}>
+        {isActive && <div style={{ width: 8, height: 8, background: '#363F4D', borderRadius: '50%' }}></div>}
+      </div>
+    );
+  };
+  return (
+    <div className="AuthHisContent">
+      <div className="AuthHisContent-title">
+        <IconFont type={'icon-qingliangtishi'} style={{ paddingRight: 12 }} />
+        调整历史
+      </div>
+      <div className="AuthHisContent-content">
+        <Timeline>
+          {hisList.map((a, index) => {
+            return (
+              <Timeline.Item key={index} dot={dotNode(index == 0 ? true : false)}>
+                <div style={{ fontWeight: 'bold', fontSize: 14, color: '#17181A', height: 14, lineHeight: '14px', marginBottom: 8 }}>{`${a.adjustUserName} ${a.adjustTime}`}</div>
+                <div style={{ height: 14, fontSize: 12, color: '#176DE6', lineHeight: '14px', marginBottom: 8, paddingLeft: 6 }}>
+                  <img style={{ width: 43, height: 14, marginRight: 4 }} src={require('../../../../../../../public/images/tiaozhenghou.png')} />
+                  {getPeriodStr(a.qualificationPeriodAfter)} {`(${a.beginDateAfter}至${a.endDateAfter})`}
+                </div>
+                <div style={{ height: 14, fontSize: 12, color: '#525866', lineHeight: '14px', marginBottom: 8, paddingLeft: 6 }}>
+                  <img style={{ width: 43, height: 14, marginRight: 4 }} src={require('../../../../../../../public/images/tiaozhengqian.png')} />
+                  {getPeriodStr(a.qualificationPeriodBefore)} {`(${a.beginDateBefore}至${a.endDateBefore})`}
+                </div>
+                {a.memo && a.memo.length > 0 && <div style={{ padding: '5px 8px', fontSize: 12, color: '#525866', background: '#F7F9FC', borderRadius: 4 }}>{a.memo}</div>}
+              </Timeline.Item>
+            );
+          })}
+        </Timeline>
+      </div>
+    </div>
+  );
+}

+ 33 - 0
src/pages/platform/setting/indicatorMana/authHisttory/style.less

@@ -0,0 +1,33 @@
+.AuthHisContent {
+  width: 400px;
+
+  .AuthHisContent-title {
+    font-weight: 500;
+    font-size: 16px;
+    height: 16px;
+    color: #17181a;
+    line-height: 16px;
+  }
+  .AuthHisContent-content {
+    max-height: 400px;
+    overflow: scroll;
+    overflow-x: hidden;
+    padding-top: 24px;
+
+    .mrms-ant-timeline-item {
+      &.mrms-ant-timeline-item-last {
+        padding-bottom: 0;
+      }
+    }
+    .mrms-ant-timeline-item-tail {
+      left: 8px;
+    }
+    .mrms-ant-timeline-item-head-custom {
+      top: 0.5px;
+      left: 9px;
+    }
+    .mrms-ant-timeline-item-content {
+      margin: 0 0 0 35px;
+    }
+  }
+}

+ 373 - 229
src/pages/platform/setting/indicatorMana/index.tsx

@@ -2,50 +2,70 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2022-07-12 11:14:21
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-12-02 14:05:44
+ * @LastEditTime: 2025-02-28 14:52:26
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/indicatorMana/index.tsx
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
 
-
-
 import { ActionType, ProColumns } from '@ant-design/pro-table';
-import React, { useEffect, useRef, useState } from 'react'
-import { Button, Divider, Dropdown, Popconfirm } from 'antd'
+import React, { useEffect, useRef, useState } from 'react';
+import { Dropdown, Empty, Input, Popconfirm, Tabs } from 'antd';
 
 import DrawerForm from './DrawerForm/drawer';
-import { DownOutlined } from '@ant-design/icons';
-import { addIndicatorManaList, delIndicatorManaList, editIndicatorManaList, getIndicatorCateList, getIndicatorManaList, IndicatorManaItemType } from '@/service/indicator';
-import TreeDirectory from './TreeDirectory';
+import { createFromIconfontCN, DownOutlined } from '@ant-design/icons';
+import {
+  addIndicatorManaList,
+  delIndicatorManaList,
+  editIndicatorManaList,
+  getIndicatorCateList,
+  getIndicatorDetailReq,
+  getIndicatorManaList,
+  IndicatorManaItemType,
+  saveIndicatorBindItems,
+} from '@/service/indicator';
 
 import './style.less';
 import { getIndicatorDictionary, IndicatorDictionaryDataType } from '@/service/dictionary';
-import { useLocation } from 'umi';
+import { useHistory, useLocation, useModel, useParams } from 'umi';
 import { KCIMLeftList } from '../../components/KCIMLeftList';
 import KCTable from '@/components/kcTable';
 import { KCInput } from '@/components/KCInput';
+import generateTableData from '../dataFilling/fillingMana/generateTableData';
+import { DATAFILL_PERIODTYPE } from '../dataFilling/fillingMana';
 
+const defaultYear = new Date().getFullYear();
+let table_columns: any[] = [];
+let total_tableDataSource: any[] = [];
 
-const IndicatorMana = () => {
+const IconFont = createFromIconfontCN({
+  scriptUrl: '',
+});
 
+const IndicatorMana = () => {
   const [reloadTable, set_reloadTable] = useState(false);
   const [drawerVisible, set_drawerVisible] = useState(false);
   const [indicateType, set_indicateType] = useState<IndicatorDictionaryDataType[]>([]);
   const [currentEditRowData, set_currentEditRowData] = useState<any | undefined>(undefined);
   const [indicatorCateTreeData, set_indicatorCateTreeData] = useState<any[]>([]);
   const [defaultExpandedRowKeys, set_defaultExpandedRowKeys] = useState<string[]>([]);
-  const [tableData, set_tableData] = useState<any[]>([]);
-  const [cateId, set_cateId] = useState<string>();
 
+  const [cateId, set_cateId] = useState<string>();
+  const [tableColumns, set_tableColumns] = useState<ProColumns[]>([]);
   const [tableDataFilterParams, set_tableDataFilterParams] = useState<any | undefined>();
   const [tableDataSearchKeywords, set_tableDataSearchKeywords] = useState<string>('');
-  const [currentSelectedLeft,set_currentSelectedLeft] = useState<any>(undefined);
-
-
-  const [drawerActype, set_drawerActype] = useState<'ADD' | 'EDIT'>('ADD');
-
+  const [currentSelectedLeft, set_currentSelectedLeft] = useState<any>(undefined);
+  const [actionType, set_actionType] = useState<'NORMAL' | 'DETAIL' | undefined>('NORMAL');
+  const [drawerActype, set_drawerActype] = useState<'ADD' | 'EDIT' | 'DATAMANA' | undefined>(undefined);
+  const [tableDataSource, set_tableDataSource] = useState<any[]>([]);
+  const [currentYear, set_currentYear] = useState(defaultYear);
+  const [currentEditRow, set_currentEditRow] = useState<any>(undefined);
+  const detailTableRef = useRef<ActionType>();
+
+  const [currentBigTab, set_currentBigTab] = useState('2');
   const indicatorTableRef = useRef<ActionType>();
-
+  const history: any = useHistory();
+  const [currentTab, set_currentTab] = useState('1');
+  const { indicatorType: indicatorUrlType }: { id: string; indicatorType: string } = history.location.query;
 
   const columns: ProColumns<any>[] = [
     {
@@ -53,7 +73,6 @@ const IndicatorMana = () => {
       dataIndex: 'name',
       hideInSearch: false,
       ellipsis: true,
-
     },
     // {
     //   title: '指标编码',
@@ -79,22 +98,21 @@ const IndicatorMana = () => {
       request: async () => {
         const resp = await getIndicatorDictionary();
         if (resp) {
-          const data = resp.filter(t => t.code == 'IndicatorType');
+          const data = resp.filter((t) => t.code == 'IndicatorType');
           if (data.length > 0) {
-            return data[0].children.map(t => ({ label: t.name, value: t.code }))
+            return data[0].children.map((t) => ({ label: t.name, value: t.code }));
           }
         }
-        return []
+        return [];
       },
       render: (text, record) => {
         if (record) {
           const { indicatorBoolean, indicatorTypeList } = record;
-          return indicatorBoolean && indicatorTypeList ? (indicatorTypeList.map((t: any) => t.name)).join('/') : ''
+          return indicatorBoolean && indicatorTypeList ? indicatorTypeList.map((t: any) => t.name).join('/') : '';
         } else {
-          return ''
+          return '';
         }
-
-      }
+      },
     },
     {
       title: '外部指标',
@@ -105,28 +123,27 @@ const IndicatorMana = () => {
       valueType: 'cascader',
       fieldProps: {
         fieldNames: { label: 'name', value: 'code' },
-        placeholder: '请选择'
+        placeholder: '请选择',
       },
 
       request: async () => {
         const resp = await getIndicatorDictionary();
         if (resp) {
-          const data = resp.filter(t => t.code == 'IndicatorExternal');
+          const data = resp.filter((t) => t.code == 'IndicatorExternal');
           if (data.length > 0) {
-            return data[0].children
+            return data[0].children;
           }
         }
-        return []
+        return [];
       },
       render: (text, record) => {
         if (record) {
           const { indicatorBoolean, indicatorExternalList } = record;
-          return indicatorBoolean ? indicatorExternalList ? (indicatorExternalList.map((t: any) => t.name)).join('/') : '-' : ''
+          return indicatorBoolean ? (indicatorExternalList ? indicatorExternalList.map((t: any) => t.name).join('/') : '-') : '';
         } else {
-          return ''
+          return '';
         }
-
-      }
+      },
     },
     {
       title: '指标编码',
@@ -134,14 +151,12 @@ const IndicatorMana = () => {
       width: 80,
       hideInSearch: true,
       ellipsis: true,
-
     },
     {
       title: '指标定义',
       dataIndex: 'targetDefinition',
       hideInSearch: true,
       ellipsis: true,
-
     },
 
     {
@@ -152,86 +167,98 @@ const IndicatorMana = () => {
       render: (text, record) => {
         const items = [
           {
-            key: '1', label: <a key="linka" href={record.indicatorPath} target='_blank'>
-              数据展示
-            </a>
+            key: '1',
+            label: (
+              <a
+                key="link"
+                onClick={() => {
+                  set_currentEditRow(record);
+                  set_actionType('DETAIL');
+                }}
+              >
+                相关数据
+              </a>
+            ),
+          },
+          {
+            key: '2',
+            label: (
+              <a key="linka" href={record.indicatorPath} target="_blank">
+                统计分析
+              </a>
+            ),
           },
           {
-            key: '3', label: <Popconfirm
-              title="是否确定删除?"
-              onConfirm={() => delHandle(record)}
-              okText="确定"
-              cancelText="取消"
-              key="link2"
-            >
-              <a>删除</a>
-            </Popconfirm>
-          }
-        ]
+            key: '3',
+            label: (
+              <Popconfirm title="是否确定删除?" onConfirm={() => delHandle(record)} okText="确定" cancelText="取消" key="link2">
+                <a>删除</a>
+              </Popconfirm>
+            ),
+          },
+        ];
 
         if (record && record.indicatorBoolean) {
           return [
             <a key="link3" onClick={() => actionHandle('EDIT', record)}>
-                管理
+              管理
             </a>,
             <Dropdown menu={{ items }}>
               <a>
-               <DownOutlined />
+                <DownOutlined />
               </a>
-            </Dropdown>
-          ]
+            </Dropdown>,
+          ];
         }
 
-        return []
-      }
-      ,
+        return [];
+      },
     },
   ];
 
+  const IconFont = createFromIconfontCN({
+    scriptUrl: '',
+  });
 
   const getIndicatorDir = async () => {
     const resp = await getIndicatorDictionary();
     if (resp) {
       set_indicateType(resp);
     }
-  }
+  };
 
   const getData = async (params: any) => {
     const { current = 1, name, indicatorType, outsideIndexes } = params;
     if (cateId) {
       const resp = await getIndicatorManaList({
-        current: 1,
-        pageSize: 0,
         menuCode: cateId as string,
+        menuType: indicatorUrlType,
         indicatorName: name,
         indicatorType: indicatorType,
-        outsideIndexes: outsideIndexes && outsideIndexes.length > 0 ? outsideIndexes[outsideIndexes.length - 1] : ''
+        outsideIndexes: outsideIndexes && outsideIndexes.length > 0 ? outsideIndexes[outsideIndexes.length - 1] : '',
       });
 
       if (resp && resp[0]) {
+        // const getName: any = (obj: any) => {
 
-        const getName: any = (obj: any) => {
-
-          let isArr = Array.isArray(obj);
-          let reslut = [];
-          if (isArr) {
-            obj.forEach((item: any) => {
-              reslut.push(...getName(item));
-            });
-          } else {
-            reslut.push(obj.menuId)
-
-            if (obj.children) {
-              reslut.push(...getName(obj.children));
-            }
-          }
-          return reslut;
-        }
+        //   let isArr = Array.isArray(obj);
+        //   let reslut = [];
+        //   if (isArr) {
+        //     obj.forEach((item: any) => {
+        //       reslut.push(...getName(item));
+        //     });
+        //   } else {
+        //     reslut.push(obj.menuId)
 
-        set_tableData(resp[0].children as unknown as []);
+        //     if (obj.children) {
+        //       reslut.push(...getName(obj.children));
+        //     }
+        //   }
+        //   return reslut;
 
+        // }
 
-        set_defaultExpandedRowKeys(getName(resp[0].children));
+        // set_defaultExpandedRowKeys(getName(resp[0].children));
 
         return {
           data: resp[0].children as unknown as [],
@@ -251,254 +278,371 @@ const IndicatorMana = () => {
       data: [],
       success: false,
       total: 0,
-    }
-
-  }
-
+    };
+  };
 
   const getIndicatorCateTree = async () => {
-    // console.log(location.search);
-    let menuId = '';
-    if (location.search.length > 0) {
-      const searchArr = location.search.split('=');
-      // console.log({ searchArr });
-      if (searchArr[1]) {
-        const resp = await getIndicatorCateList({ menuCode: searchArr[1] });
-        if (resp) {
-          set_indicatorCateTreeData(resp);
-        }
+    if (indicatorUrlType) {
+      const resp = await getIndicatorCateList({ menuType: indicatorUrlType });
+      if (resp) {
+        set_indicatorCateTreeData(resp);
+      } else {
+        set_indicatorCateTreeData([]);
+        indicatorTableRef.current?.reload();
       }
-
     }
-
-  }
-
-
+  };
 
   const actionHandle = async (key: 'EDIT' | 'DEL', record: IndicatorDictionaryDataType) => {
-
     set_currentEditRowData(record);
     if (key == 'EDIT') {
       set_drawerActype('EDIT');
       set_drawerVisible(true);
     }
-  }
+  };
 
   const addHandle = () => {
     set_drawerActype('ADD');
     set_currentEditRowData(undefined);
     set_drawerVisible(true);
-  }
+  };
 
   const delHandle = async (record: IndicatorDictionaryDataType) => {
-
     const resp = await delIndicatorManaList({ id: record.id });
     if (resp) {
       indicatorTableRef.current?.reload();
     }
-  }
+  };
 
   const onVisibleChangeHandle = (bool: boolean) => {
-
     if (!bool) {
       set_drawerVisible(false);
     }
     if (bool) {
       set_drawerVisible(true);
     }
-  }
+  };
 
   const drawerFormCommitHanndle = async (data: any) => {
-    // const formData = Object.assign({}, data[0].baseInfoformRef, data[1].manaInfoformRef, data[2].showSetInfoformRef, data[3].adminInfoformRef);
-    //const _formData = {...data[0].baseInfoformRef,...data[1].manaInfoformRef,...data[2].showSetInfoformRef,...data[3].adminInfoformRef}
-
-    let formData: any;
-    data.forEach((element: any) => {
-      if (element.baseInfoformRef) {
-        formData = { ...formData, ...element.baseInfoformRef }
-      }
-      if (element.manaInfoformRef) {
-        formData = { ...formData, ...element.manaInfoformRef }
-      }
-      if (element.showSetInfoformRef) {
-        formData = { ...formData, ...element.showSetInfoformRef }
-      }
-      if (element.adminInfoformRef) {
-        formData = { ...formData, ...element.adminInfoformRef }
-      }
-    });
-
-
-    if (drawerActype == 'ADD') {
-      const resp = await addIndicatorManaList({
-        ...formData,
-        caseBreakdown: formData.caseBreakdown ? formData.caseBreakdown.join('/') : '',
-        dataDownload: formData.dataDownload ? formData.dataDownload.join('/') : '',
-        drillLevel: formData.drillLevel ? formData.drillLevel.join('/') : '',
-        dataSum: formData.dataSum ? formData.dataSum.join('/') : '',
-        chartType: formData.chartType ? formData.chartType.join('/') : '',
-
-        indicatorMenuList: formData.indicatorMenuList ? formData.indicatorMenuList : [],
-        indicatorTypeList: formData.indicatorTypeList ? formData.indicatorTypeList : [],
-        indicatorExternalList: formData.indicatorExternalList ? formData.indicatorExternalList : [],
-        divisionId: `${formData.divisionId}`,
-
-      });
-
+    if (drawerActype == 'DATAMANA') {
+      const resp = await saveIndicatorBindItems(data);
       if (resp) {
         set_drawerVisible(false);
-        indicatorTableRef.current?.reload();
+        getIndicatorDetail();
       }
     }
 
-    if (drawerActype == 'EDIT') {
-      const resp = await editIndicatorManaList({
-        ...currentEditRowData,
-        ...formData,
-        caseBreakdown: formData.caseBreakdown ? formData.caseBreakdown.join('/') : '',
-        dataDownload: formData.dataDownload ? formData.dataDownload.join('/') : '',
-        drillLevel: formData.drillLevel ? formData.drillLevel.join('/') : '',
-        dataSum: formData.dataSum ? formData.dataSum.join('/') : '',
-        chartType: formData.chartType ? formData.chartType.join('/') : '',
+    if (drawerActype != 'DATAMANA') {
+      let formData: any;
+      data.forEach((element: any) => {
+        if (element.baseInfoformRef) {
+          formData = { ...formData, ...element.baseInfoformRef };
+        }
+        if (element.manaInfoformRef) {
+          formData = { ...formData, ...element.manaInfoformRef };
+        }
+        if (element.showSetInfoformRef) {
+          formData = { ...formData, ...element.showSetInfoformRef };
+        }
+        if (element.adminInfoformRef) {
+          formData = { ...formData, ...element.adminInfoformRef };
+        }
       });
-      if (resp) {
-        set_drawerVisible(false);
-        indicatorTableRef.current?.reload();
-      }
-    }
 
-  }
+      if (drawerActype == 'ADD') {
+        const resp = await addIndicatorManaList({
+          ...formData,
+          caseBreakdown: formData.caseBreakdown ? formData.caseBreakdown.join('/') : '',
+          dataDownload: formData.dataDownload ? formData.dataDownload.join('/') : '',
+          drillLevel: formData.drillLevel ? formData.drillLevel.join('/') : '',
+          dataSum: formData.dataSum ? formData.dataSum.join('/') : '',
+          chartType: formData.chartType ? formData.chartType.join('/') : '',
+
+          indicatorMenuList: formData.indicatorMenuList ? formData.indicatorMenuList : [],
+          indicatorTypeList: formData.indicatorTypeList ? formData.indicatorTypeList : [],
+          indicatorExternalList: formData.indicatorExternalList ? formData.indicatorExternalList : [],
+          divisionId: `${formData.divisionId}`,
+        });
 
+        if (resp) {
+          set_drawerVisible(false);
+          indicatorTableRef.current?.reload();
+        }
+      }
+
+      if (drawerActype == 'EDIT') {
+        const resp = await editIndicatorManaList({
+          ...currentEditRowData,
+          ...formData,
+          caseBreakdown: formData.caseBreakdown ? formData.caseBreakdown.join('/') : '',
+          dataDownload: formData.dataDownload ? formData.dataDownload.join('/') : '',
+          drillLevel: formData.drillLevel ? formData.drillLevel.join('/') : '',
+          dataSum: formData.dataSum ? formData.dataSum.join('/') : '',
+          chartType: formData.chartType ? formData.chartType.join('/') : '',
+        });
+        if (resp) {
+          set_drawerVisible(false);
+          indicatorTableRef.current?.reload();
+        }
+      }
+    }
+  };
 
   const onSelectChangehandle = (currentSelected: any) => {
     set_currentSelectedLeft(currentSelected);
     currentSelected && set_cateId(currentSelected.code);
-  }
+  };
 
   const tableDataSearchHandle = (paramName: string) => {
     set_tableDataFilterParams({
       ...tableDataFilterParams,
-      current:1,
+      current: 1,
       [`${paramName}`]: tableDataSearchKeywords,
     });
   };
 
-  useEffect(() => {
-    if (tableData) {
-      const keys = tableData.map((t: any) => t ? t.code : '');
+  const detailTableDataSearchHandle = (paramName: string) => {
+    const result = total_tableDataSource.filter((a: any) => a[`${paramName}`].indexOf(tableDataSearchKeywords) != -1);
+    set_tableDataSource(result);
+  };
+
+  const tabChangeHandle = (key: string) => {
+    set_currentTab(key);
+  };
 
+  const getIndicatorDetail = async () => {
+    if (currentEditRow) {
+      set_tableColumns([]);
+      set_tableDataSource([]);
+      const resp = await getIndicatorDetailReq({ indicatorCode: currentEditRow.code, dataYear: `${currentYear}`, periodType: currentTab });
+      if (resp) {
+        const { itemTitles, itemDatas } = resp;
+        const { columns, dataSource } = generateTableData(itemTitles, itemDatas);
+        table_columns = columns;
+        total_tableDataSource = dataSource;
+        set_tableColumns(columns);
+        set_tableDataSource(dataSource);
+      }
     }
-  }, [tableData]);
+  };
 
+  const dataManaBtnHandle = () => {
+    set_drawerActype('DATAMANA');
+    set_currentEditRowData({ ...currentEditRow });
+    set_drawerVisible(true);
+  };
 
   useEffect(() => {
-    // console.log({ cateId });
-    // set_reloadTable(true);
-    indicatorTableRef.current?.reload();
+    if (cateId) {
+      indicatorTableRef.current?.reload();
+    }
   }, [cateId]);
 
+  useEffect(() => {
+    if (!currentEditRow) {
+      set_actionType('NORMAL');
+    }
+  }, [currentEditRow]);
+
+  useEffect(() => {
+    if (actionType == 'DETAIL' && currentBigTab === '2') {
+      getIndicatorDetail();
+    }
+  }, [actionType, currentBigTab]);
+
+  useEffect(() => {
+    getIndicatorDetail();
+  }, [currentTab, currentYear]);
 
   useEffect(() => {
+    set_cateId(undefined);
+    set_actionType(undefined);
+    set_drawerActype(undefined);
+    set_currentBigTab('1');
+    set_currentEditRow(undefined);
+    set_tableColumns([]);
+    set_tableDataSource([]);
+    set_currentEditRowData(undefined);
+
     getIndicatorCateTree();
   }, [location.search]);
 
+  useEffect(() => {
+    if (!drawerVisible) {
+      set_drawerActype(undefined);
+      set_currentEditRowData(undefined);
+    }
+  }, [drawerVisible]);
 
   useEffect(() => {
     getIndicatorDir();
     getIndicatorCateTree();
-  }, [])
+  }, []);
 
   return (
-    <div className='IndicatorMana'>
-      <DrawerForm actType={drawerActype} visible={drawerVisible} onVisibleChange={onVisibleChangeHandle} onFinish={data => drawerFormCommitHanndle(data)} record={currentEditRowData} />
-      <div className='content'>
-        <div className='left'>
-          {/* <TreeDirectory data={indicatorCateTreeData} onSelectChange={(info) => onSelectChangehandle(info)} /> */}
-          <KCIMLeftList searchKey={'name'} listType='tree' dataSource={indicatorCateTreeData}
-            fieldNames={{ title: 'name', key: 'code' }} contentH={'100%'}
-            onChange={(info) => onSelectChangehandle(info)}
-          />
-        </div>
-        <div className='right'>
-          <div className='tableTitle'>{currentSelectedLeft&&currentSelectedLeft.name}</div>
-          <div className="toolBar">
-            <div className="filter">
-              <div className="filterItem">
-                <KCInput
-                  placeholder={'指标名称'}
-                  style={{ width: 240 }}
-                  search
-                  allowClear
-                  onChange={(e) => {
-                    set_tableDataSearchKeywords(e.target.value);
-                    if (e.target.value.length == 0) {
-                      set_tableDataFilterParams({
-                        ...tableDataFilterParams,
-                        current:1,
-                        name: '',
-                      });
-                    }
-                  }}
-                  onSearch={() => tableDataSearchHandle('name')}
-                />
+    <div className="IndicatorMana">
+      <DrawerForm actType={drawerActype} visible={drawerVisible} onVisibleChange={onVisibleChangeHandle} onFinish={(data) => drawerFormCommitHanndle(data)} record={currentEditRowData} />
+
+      {actionType == 'DETAIL' && (
+        <div className="FillingMana-detail">
+          <div className="FillingMana-detail-header">
+            <div className="FillingMana-detail-header-title">
+              <div className="backBtn" onClick={() => set_currentEditRow(undefined)}>
+                <IconFont style={{ fontSize: 15 }} type={'iconfanhui'} />
               </div>
+              <span>{currentEditRow?.name}</span>
             </div>
-            <div className="btnGroup">
-              <span className="add" onClick={addHandle}>
-                新增
-              </span>
+            <div className="FillingMana-detail-header-title-sub">
+              <span>指标编码:{currentEditRow?.code}</span>
+              <span>指标定义:{currentEditRow?.targetDefinition}</span>
             </div>
           </div>
-          {
-            cateId && (
+          <div className="bigTab" title={`${currentBigTab == '1'}`}>
+            <div className={currentBigTab === '1' ? 'tab on' : 'tab'} onClick={() => set_currentBigTab('1')}>
+              线上数据
+            </div>
+            <div className={currentBigTab === '2' ? 'tab on' : 'tab'} onClick={() => set_currentBigTab('2')}>
+              填报数据
+            </div>
+          </div>
+
+          {currentBigTab === '1' && (
+            <div className="currentBigTab1content">
+              <img src={require('../../../../../public/images/empty_bigger.png')} alt="" />
+              <div className="detail-noAuthRecord-title">暂无内容</div>
+            </div>
+          )}
+
+          {currentBigTab === '2' && (
+            <div className="FillingMana-detail-content">
+              <div className="tabBar">
+                <Tabs defaultActiveKey={currentTab} onChange={tabChangeHandle} items={DATAFILL_PERIODTYPE.map((a) => ({ label: a.label, key: `${a.value}` }))} />
+                <div className="dataManaBtn" onClick={() => dataManaBtnHandle()}>
+                  <IconFont type={'iconpeizhi'} style={{ position: 'relative', top: 1, paddingRight: 4 }} />
+                  数据管理
+                </div>
+              </div>
 
+              <div className="toolBar">
+                <div className="filter">
+                  <div className="filterItem">
+                    <span className="label" style={{ whiteSpace: 'nowrap' }}>
+                      {' '}
+                      检索:
+                    </span>
+                    <Input
+                      placeholder={'填报科室'}
+                      allowClear
+                      autoComplete="off"
+                      suffix={<IconFont type="iconsousuo" style={{ color: '#99A6BF' }} onClick={() => detailTableDataSearchHandle('fillItemName')} />}
+                      onChange={(e) => {
+                        set_tableDataSearchKeywords(e.target.value);
+                        if (e.target.value.length == 0) {
+                          set_tableDataSource(total_tableDataSource);
+                        }
+                      }}
+                      onPressEnter={(e: any) => {
+                        const result = total_tableDataSource.filter((a: any) => a.fillItemName.indexOf(e.target.value) != -1);
+                        set_tableDataSource(result);
+                      }}
+                    />
+                  </div>
+                </div>
+                <div className="btnGroup">
+                  <div className="changeBtn">
+                    <div className="actBtn" onClick={() => set_currentYear(currentYear - 1)}>
+                      <IconFont type={'iconshuangzuo'} />
+                    </div>
+                    <span style={{ padding: '0 8px' }}>{currentYear}</span>
+                    <div className="actBtn" onClick={() => set_currentYear(currentYear + 1)}>
+                      <IconFont type={'iconshuangyou'} />
+                    </div>
+                  </div>
+                </div>
+              </div>
+              <KCTable newVer actionRef={detailTableRef} columns={[...tableColumns]} scroll={{ y: 'calc(100vh - 232px)' }} rowKey="id" dataSource={tableDataSource} pagination={false} />
+            </div>
+          )}
+        </div>
+      )}
+      {actionType != 'DETAIL' && (
+        <div className="content">
+          <div className="left">
+            {/* <TreeDirectory data={indicatorCateTreeData} onSelectChange={(info) => onSelectChangehandle(info)} /> */}
+            <KCIMLeftList
+              searchKey={'name'}
+              listType="tree"
+              dataSource={indicatorCateTreeData}
+              fieldNames={{ title: 'name', key: 'code' }}
+              contentH={'100%'}
+              onChange={(info) => onSelectChangehandle(info)}
+            />
+          </div>
+          <div className="right">
+            <div className="tableTitle">{currentSelectedLeft && currentSelectedLeft.name}</div>
+            <div className="toolBar">
+              <div className="filter">
+                <div className="filterItem">
+                  <KCInput
+                    placeholder={'指标名称'}
+                    style={{ width: 240 }}
+                    search
+                    allowClear
+                    onChange={(e) => {
+                      set_tableDataSearchKeywords(e.target.value);
+                      if (e.target.value.length == 0) {
+                        set_tableDataFilterParams({
+                          ...tableDataFilterParams,
+                          current: 1,
+                          name: '',
+                        });
+                      }
+                    }}
+                    onSearch={() => tableDataSearchHandle('name')}
+                  />
+                </div>
+              </div>
+              <div className="btnGroup">
+                <span className="add" onClick={addHandle}>
+                  新增
+                </span>
+              </div>
+            </div>
+            {cateId && (
               <KCTable
                 newVer
                 rowKey="menuId"
                 columns={columns}
                 params={tableDataFilterParams}
                 actionRef={indicatorTableRef}
-                scroll={{y:`calc(100vh - 228px)`}}
+                scroll={{ y: `calc(100vh - 228px)` }}
                 expandable={{
                   expandedRowKeys: defaultExpandedRowKeys,
                   onExpand: (expanded, record) => {
                     // console.log({expanded, record});
                     let _expandedKeys = [...defaultExpandedRowKeys];
                     if (expanded) {
-                      _expandedKeys.push(record.menuId)
+                      _expandedKeys.push(record.menuId);
                     } else {
-                      const index = _expandedKeys.findIndex(t => t == `${record.menuId}`);
+                      const index = _expandedKeys.findIndex((t) => t == `${record.menuId}`);
                       if (index != -1) {
                         _expandedKeys.splice(index, 1);
                       }
                     }
                     set_defaultExpandedRowKeys([..._expandedKeys]);
-                  }
-
+                  },
                 }}
                 reload={reloadTable}
                 pagination={false}
-                // toolBarRender={() => [
-                //   // <Button key="1" onClick={addHandle}>
-                //   //   上传
-                //   // </Button>,
-                //   // <Button key="2" onClick={addHandle}>
-                //   //   下载
-                //   // </Button>,
-                //   <Button key="3" type="primary" onClick={addHandle}>
-                //     新增
-                //   </Button>,
-                // ]}
                 options={false}
                 request={(params) => getData(params)}
               />
-            )
-          }
+            )}
+          </div>
         </div>
-      </div>
-
+      )}
     </div>
-  )
-}
+  );
+};
 
-export default IndicatorMana
+export default IndicatorMana;

+ 245 - 31
src/pages/platform/setting/indicatorMana/style.less

@@ -1,5 +1,222 @@
 .IndicatorMana {
   width: 100%;
+  .bigTab {
+    display: flex;
+    flex-direction: row;
+    justify-content: flex-start;
+    align-items: center;
+    padding-left: 8px;
+    .tab {
+      cursor: pointer;
+      text-align: center;
+      width: 80px;
+      height: 32px;
+      line-height: 32px;
+      background: #e6eaf2;
+      border-radius: 8px 8px 0px 0px;
+      font-weight: 400;
+      font-size: 14px;
+      color: #17181a;
+      margin-right: 4px;
+      &.on {
+        color: #3377ff;
+        background: #fff;
+      }
+    }
+  }
+  .FillingMana-detail {
+    .FillingMana-detail-header {
+      height: 80px;
+      padding: 16px;
+      background: #ffffff;
+      border-radius: 4px;
+      margin-bottom: 16px;
+
+      .FillingMana-detail-header-title {
+        display: flex;
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: center;
+        margin-bottom: 10px;
+
+        .backBtn {
+          cursor: pointer;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          width: 24px;
+          height: 24px;
+          background: #fafcff;
+          border-radius: 4px;
+          margin-right: 12px;
+          border: 1px solid #dae2f2;
+        }
+
+        & > span {
+          font-weight: bold;
+          font-size: 20px;
+          color: #17181a;
+          height: 20px;
+          line-height: 20px;
+        }
+      }
+
+      .FillingMana-detail-header-title-sub {
+        display: flex;
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: center;
+        padding-left: 36px;
+
+        & > span {
+          display: inline-block;
+          height: 14px;
+          line-height: 14px;
+          font-weight: 400;
+          font-size: 14px;
+          color: #525866;
+          margin-right: 24px;
+
+          &:last-child {
+            display: inline-block;
+            width: calc(100% - 200px);
+            overflow: hidden;
+            text-wrap: nowrap;
+            text-overflow: ellipsis;
+          }
+        }
+      }
+    }
+
+    .currentBigTab1content {
+      display: flex;
+      width: 100%;
+      height: calc(100vh - 210px);
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+      padding: 16px;
+      background: #ffffff;
+      border-radius: 4px;
+
+      & > img {
+        width: 160px;
+        height: 140px;
+      }
+
+      .detail-noAuthRecord-title {
+        font-weight: 500;
+        font-size: 24px;
+        height: 24px;
+        color: #17181a;
+        line-height: 24px;
+        margin-top: 24px;
+        margin-bottom: 16px;
+      }
+
+      .detail-noAuthRecord-title-sub {
+        font-weight: 400;
+        font-size: 14px;
+        height: 14px;
+        color: #7a8599;
+        line-height: 14px;
+      }
+
+      .backBtn {
+        width: 56px;
+        height: 24px;
+        text-align: center;
+        margin-top: 24px;
+        background: #3377ff;
+        border-radius: 4px;
+        font-weight: 400;
+        font-size: 14px;
+        color: #ffffff;
+        line-height: 24px;
+        cursor: pointer;
+      }
+    }
+
+    .FillingMana-detail-content {
+      padding: 16px;
+      padding-top: 0;
+      background: #ffffff;
+      border-radius: 4px;
+
+      .tabBar {
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+        align-items: center;
+
+        .dataManaBtn {
+          cursor: pointer;
+          font-weight: 400;
+          font-size: 14px;
+          color: #3376fe;
+        }
+
+        .kcmp-ant-tabs {
+          .kcmp-ant-tabs-nav {
+            &::before {
+              border-bottom: none;
+            }
+
+            .kcmp-ant-tabs-nav-wrap {
+              .kcmp-ant-tabs-nav-list {
+                .kcmp-ant-tabs-tab {
+                  padding-bottom: 5px;
+                }
+              }
+            }
+          }
+        }
+      }
+
+      .toolBar {
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 16px;
+
+        .filter {
+          display: flex;
+          flex-direction: row;
+          justify-content: flex-start;
+          align-items: center;
+
+          .filterItem {
+            display: flex;
+            flex-direction: row;
+            justify-content: center;
+            align-items: center;
+          }
+        }
+
+        .btnGroup {
+          .changeBtn {
+            display: flex;
+            flex-direction: row;
+            justify-content: flex-start;
+            align-items: center;
+
+            .actBtn {
+              cursor: pointer;
+              display: flex;
+              justify-content: center;
+              align-items: center;
+              width: 24px;
+              height: 24px;
+              background: #fafcff;
+              border-radius: 4px;
+              border: 1px solid #dae2f2;
+            }
+          }
+        }
+      }
+    }
+  }
 
   .content {
     display: flex;
@@ -12,7 +229,7 @@
 
     .left {
       width: 220px;
-      height:calc(100vh - 80px);
+      height: calc(100vh - 80px);
       padding: 0px;
       padding-top: 8px;
       border-radius: 4px;
@@ -27,51 +244,48 @@
 
       .tableTitle {
         height: 16px;
-        font-weight:bold;
+        font-weight: bold;
         font-size: 16px;
-        color: #17181A;
+        color: #17181a;
         line-height: 16px;
         margin-bottom: 16px;
       }
 
       .toolBar {
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 16px;
+
+        .filter {
           display: flex;
           flex-direction: row;
-          justify-content: space-between;
+          justify-content: flex-start;
           align-items: center;
-          margin-bottom: 16px;
-      
-          .filter {
+
+          .filterItem {
             display: flex;
             flex-direction: row;
-            justify-content: flex-start;
+            justify-content: center;
             align-items: center;
-      
-            .filterItem {
-              display: flex;
-              flex-direction: row;
-              justify-content: center;
-              align-items: center;
-            }
           }
-      
-          .btnGroup {
-            .add {
-              cursor: pointer;
-              display: inline-block;
-              font-size: 14px;
-              font-weight: 400;
-              color: #FFFFFF;
-              line-height: 24px;
-              padding: 0 14px;
-              background: #3377FF;
-              border-radius: 4px;
-            }
+        }
+
+        .btnGroup {
+          .add {
+            cursor: pointer;
+            display: inline-block;
+            font-size: 14px;
+            font-weight: 400;
+            color: #ffffff;
+            line-height: 24px;
+            padding: 0 14px;
+            background: #3377ff;
+            border-radius: 4px;
           }
-      
         }
+      }
     }
   }
-
-
 }

+ 19 - 20
src/pages/platform/setting/pubDicMana/index.tsx

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2023-03-03 11:30:33
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-12-02 14:09:41
+ * @LastEditTime: 2025-02-18 15:10:18
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/index.tsx
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -28,7 +28,7 @@ import { DataNode } from 'antd/lib/tree';
 import DirectoryTree from 'antd/es/tree/DirectoryTree';
 
 const SearchIcon = createFromIconfontCN({
-  scriptUrl: '//at.alicdn.com/t/c/font_1927152_g1njmm1kh7b.js',
+  scriptUrl: '',
 });
 
 let systemId = '';
@@ -134,9 +134,9 @@ const PubDicMana = () => {
     const currentSelectedHop = localStorage.getItem('currentSelectedSubHop');
     const { systemId: parentId } = currentSelectedTreeNode;
     let hospId = '0';
-    if (currentSelectedHop&&pageType == 2) {
-        const { id } = JSON.parse(currentSelectedHop);
-        hospId = id
+    if (currentSelectedHop && pageType == 2) {
+      const { id } = JSON.parse(currentSelectedHop);
+      hospId = id;
     }
     if (type == 'EDIT' && pageType) {
       const resp = await editPubDicRelaTbaleData(
@@ -157,16 +157,16 @@ const PubDicMana = () => {
       }
     }
 
-    return true
+    return true;
   };
 
   const UpDataActBtn = ({ record, type }: { record: any; type: 'EDIT' | 'ADD' }) => {
     let tableFileNames = [];
     if (currentSelectedTreeNode) {
       const { topic = '' } = currentSelectedTreeNode;
-      const titles = topic.split('|')
+      const titles = topic.split('|');
       if (topic && titles.length == 7) {
-        tableFileNames = titles
+        tableFileNames = titles;
       }
     }
     return (
@@ -179,14 +179,12 @@ const PubDicMana = () => {
           return updateTable(type == 'EDIT' ? (pageType == 1 ? { ...val, dictDataId: record.dictDataId } : { ...val, id: record.id }) : val, type);
         }}
       >
-        <ProFormText name="name" label={tableFileNames.length == 7?`${tableFileNames[0]}:`:"名称:"} placeholder="请输入" rules={[{ required: true, message: '名称不能为空!' }]} />
-        <ProFormText name="code" label={tableFileNames.length == 7?`${tableFileNames[1]}:`:"代码:"} placeholder="请输入" rules={[{ required: true, message: '代码不能为空!' }]} />
-        <ProFormText name="value" label={tableFileNames.length == 7?`${tableFileNames[2]}:`:"对应值:"} placeholder="请输入" />
-        <ProFormDigit label={tableFileNames.length == 7?`${tableFileNames[3]}:`:"顺序号:"} name={pageType == 1 ? 'dictSort' : 'sort'} rules={[{ required: true, message: '顺序号不能为空!' }]} />
-
+        <ProFormText name="name" label={tableFileNames.length == 7 ? `${tableFileNames[0]}:` : '名称:'} placeholder="请输入" rules={[{ required: true, message: '名称不能为空!' }]} />
+        <ProFormText name="code" label={tableFileNames.length == 7 ? `${tableFileNames[1]}:` : '代码:'} placeholder="请输入" rules={[{ required: true, message: '代码不能为空!' }]} />
+        <ProFormText name="value" label={tableFileNames.length == 7 ? `${tableFileNames[2]}:` : '对应值:'} placeholder="请输入" />
         <ProFormRadio.Group
           name={pageType == 1 ? 'dictDefault' : 'defaultFlag'}
-          label={tableFileNames.length == 7?`${tableFileNames[4]}:`:"默认:"}
+          label={tableFileNames.length == 7 ? `${tableFileNames[3]}:` : '默认:'}
           fieldProps={{
             buttonStyle: 'solid',
           }}
@@ -202,10 +200,12 @@ const PubDicMana = () => {
           ]}
           rules={[{ required: true, message: '默认不能为空!' }]}
         />
+        <ProFormDigit label={tableFileNames.length == 7 ? `${tableFileNames[4]}:` : '顺序号:'} name={pageType == 1 ? 'dictSort' : 'sort'} rules={[{ required: true, message: '顺序号不能为空!' }]} />
+
         {true && (
           <>
-            <ProFormText name="expandOne" label={tableFileNames.length == 7?`${tableFileNames[5]}:`:"扩展一:"} placeholder="请输入" />
-            <ProFormText name="expandTwo" label={tableFileNames.length == 7?`${tableFileNames[6]}:`:"扩展二:"} placeholder="请输入" />
+            <ProFormText name="expandOne" label={tableFileNames.length == 7 ? `${tableFileNames[5]}:` : '扩展一:'} placeholder="请输入" />
+            <ProFormText name="expandTwo" label={tableFileNames.length == 7 ? `${tableFileNames[6]}:` : '扩展二:'} placeholder="请输入" />
           </>
         )}
       </ModalForm>
@@ -215,7 +215,7 @@ const PubDicMana = () => {
   const tableDataSearchHandle = (paramName: string) => {
     set_tableDataFilterParams({
       ...tableDataFilterParams,
-      current:1,
+      current: 1,
       [`${paramName}`]: tableDataSearchKeywords,
     });
   };
@@ -276,7 +276,6 @@ const PubDicMana = () => {
     setExpandedKeys(newExpandedKeys as React.Key[]);
     setSearchValue(value);
     setAutoExpandParent(true);
-    
   };
 
   const onSelect: TreeProps['onSelect'] = (selectedKeys, info) => {
@@ -439,10 +438,10 @@ const PubDicMana = () => {
                       pageType == 1
                         ? {
                             ...tableDataFilterParams,
-                            current:1,
+                            current: 1,
                             typeName: '',
                           }
-                        : { ...tableDataFilterParams,current:1, dictName: '' },
+                        : { ...tableDataFilterParams, current: 1, dictName: '' },
                     );
                   }
                 }}

+ 15 - 7
src/pages/platform/setting/pubDicTypeMana/index.tsx

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2023-03-03 11:30:33
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-12-06 16:39:19
+ * @LastEditTime: 2025-02-18 15:10:34
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/index.tsx
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -27,7 +27,7 @@ import DirectoryTree from 'antd/es/tree/DirectoryTree';
 import { getDeepestTreeData } from '@/utils';
 
 const SearchIcon = createFromIconfontCN({
-  scriptUrl: '//at.alicdn.com/t/c/font_1927152_g1njmm1kh7b.js',
+  scriptUrl: '',
 });
 
 export default function PubDicTypeMana() {
@@ -91,7 +91,6 @@ export default function PubDicTypeMana() {
   };
 
   const getTableData = async (params: any) => {
-
     const resp = await getData({ ...params, systemId: params.systemId ? params.systemId : currentSelectedTreeNode.code });
     set_reload(false);
     if (resp) {
@@ -194,7 +193,7 @@ export default function PubDicTypeMana() {
   const tableDataSearchHandle = (paramName: string) => {
     set_tableDataFilterParams({
       ...tableDataFilterParams,
-      current:1,
+      current: 1,
       [`${paramName}`]: tableDataSearchKeywords,
     });
   };
@@ -302,7 +301,6 @@ export default function PubDicTypeMana() {
             blockNode={true}
             icon={() => null}
             titleRender={(nodeData: any) => {
-              
               const strTitle = nodeData.name as string;
               const index = strTitle.indexOf(searchValue);
               const beforeStr = strTitle.substring(0, index);
@@ -386,7 +384,7 @@ export default function PubDicTypeMana() {
                   if (e.target.value.length == 0) {
                     set_tableDataFilterParams({
                       ...tableDataFilterParams,
-                      current:1,
+                      current: 1,
                       typeName: '',
                     });
                   }
@@ -400,7 +398,17 @@ export default function PubDicTypeMana() {
           </div>
         </div>
         <div style={{ marginTop: 16 }}>
-          {currentSelectedTreeNode && <KCTable  scroll={{y:`calc(100vh - 255px)`}} columns={columns as ProColumns[]} reload={reload} rowKey="dictId" newVer params={tableDataFilterParams} request={(params) => getTableData(params)} />}
+          {currentSelectedTreeNode && (
+            <KCTable
+              scroll={{ y: `calc(100vh - 255px)` }}
+              columns={columns as ProColumns[]}
+              reload={reload}
+              rowKey="dictId"
+              newVer
+              params={tableDataFilterParams}
+              request={(params) => getTableData(params)}
+            />
+          )}
         </div>
       </div>
     </div>

+ 42 - 26
src/pages/platform/setting/roleManage/index.tsx

@@ -1,14 +1,14 @@
 /*
  * @Author: your name
  * @Date: 2022-01-13 15:22:48
- * @LastEditTime: 2024-12-02 14:11:50
+ * @LastEditTime: 2025-02-21 17:50:31
  * @LastEditors: code4eat awesomedema@gmail.com
  * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/hospManage/index.tsx
  */
 
 import { FC, Key, useEffect, useState } from 'react';
-import { roleManageModelState, ConnectProps, Loading, connect } from 'umi';
+import { roleManageModelState, ConnectProps, Loading, connect, useModel } from 'umi';
 import { Checkbox, Divider, Dropdown, Input, Popconfirm, Switch, Transfer, TreeProps, Modal } from 'antd';
 import KCTable from '@/components/kcTable';
 import type { ProColumns } from '@ant-design/pro-table';
@@ -53,12 +53,13 @@ interface TreeNode {
 }
 
 const SearchIcon = createFromIconfontCN({
-  scriptUrl: '//at.alicdn.com/t/c/font_1927152_g1njmm1kh7b.js',
+  scriptUrl: '',
 });
 
 const selectSystem: { systemId: string; type: number }[] = [];
 
 const DrawerActBtn = ({ record }: { record: any }) => {
+  const { initialState, setInitialState } = useModel('@@initialState');
   const [treeData, set_treeData] = useState<getTreeDataRespType[]>([]);
   const [currentSelectedTreeNode, set_currentSelectedTreeNode] = useState<any | undefined>(undefined);
   const [drawerTablereload, set_drawerTablereload] = useState(false);
@@ -372,7 +373,6 @@ const DrawerActBtn = ({ record }: { record: any }) => {
       function: [],
     }));
 
-
     const data = {
       hospId: record.hospId,
       roleId: record.roleId,
@@ -381,8 +381,6 @@ const DrawerActBtn = ({ record }: { record: any }) => {
       functionList: [...result, ...needCancelMenus],
     };
 
- 
-
     const resp = await saveRoleRelaApiPerm(data);
     if (resp) {
       set_drawerTablereload(true);
@@ -399,7 +397,7 @@ const DrawerActBtn = ({ record }: { record: any }) => {
   const drawerTableDataSearchHandle = (paramName: string) => {
     set_drawerTableDataFilterParams({
       ...drawerTableDataFilterParams,
-      current:1,
+      current: 1,
       [`${paramName}`]: drawerTableDataSearchKeywords,
     });
   };
@@ -417,7 +415,7 @@ const DrawerActBtn = ({ record }: { record: any }) => {
 
       setInitCheckData();
 
-      set_drawerTableDataFilterParams({ ...drawerTableDataFilterParams,current:1, systemId: currentSelectedTreeNode.code });
+      set_drawerTableDataFilterParams({ ...drawerTableDataFilterParams, current: 1, systemId: currentSelectedTreeNode.code });
       //切换系统清空数据
       set_checkBoxCodes([]);
       set_checkedTableMenuIds([]);
@@ -568,7 +566,7 @@ const DrawerActBtn = ({ record }: { record: any }) => {
                         if (e.target.value.length == 0) {
                           set_drawerTableDataFilterParams({
                             ...drawerTableDataFilterParams,
-                            current:1,
+                            current: 1,
                             name: '',
                           });
                         }
@@ -633,10 +631,9 @@ const DrawerActBtn = ({ record }: { record: any }) => {
 
                       if (record.children) {
                         const childIds = extractAttributeValues(record, 'menuId');
-                       
+
                         set_checkedMenuParentsIds([...checkedMenuParentsIds, ...childIds, ...parentsIds]);
                       } else {
-                        
                         set_checkedMenuParentsIds([...checkedMenuParentsIds, record.menuId, ...parentsIds]);
                       }
 
@@ -680,7 +677,6 @@ const DrawerActBtn = ({ record }: { record: any }) => {
                       set_checkedTableMenuIds([...leftCheckedMenuIds]);
 
                       set_checkBoxCodes([...uniqueFunc(filtedCheckCodes, 'menuId')]);
-                    
                     }
                   },
                 }}
@@ -702,7 +698,7 @@ const DrawerActBtn = ({ record }: { record: any }) => {
 
 const RoleManage: FC<PageProps> = ({ roleManageModel: state, dispatch }) => {
   const { reloadTable } = state;
-
+  const { initialState, setInitialState } = useModel('@@initialState');
   const [tableDataFilterParams, set_tableDataFilterParams] = useState<any | undefined>();
   const [tableDataSearchKeywords, set_tableDataSearchKeywords] = useState<string>('');
 
@@ -713,10 +709,10 @@ const RoleManage: FC<PageProps> = ({ roleManageModel: state, dispatch }) => {
       hideInTable: true,
     },
     {
-      title: '院区名称',
+      title: initialState?.customerType == '2' ? '园区名称' : '院区名称',
       dataIndex: 'hospName',
-      ellipsis:true,
-      width:200
+      ellipsis: true,
+      width: 200,
     },
     {
       title: '角色名称',
@@ -732,18 +728,38 @@ const RoleManage: FC<PageProps> = ({ roleManageModel: state, dispatch }) => {
       title: '备注',
       dataIndex: 'remark',
     },
-    {
-      title: '变更人',
-      dataIndex: 'hospAbbreviation',
-    },
     {
       title: '有数账号',
       dataIndex: 'account',
     },
     {
-      title: '变更日期',
-      dataIndex: 'modifyTime',
-      ellipsis:true
+      title: '数据权限',
+      dataIndex: 'dataPermissionName',
+    },
+    {
+      title: '角色标签',
+      renderText(text, record: any) {
+        const { roleTags = [] } = record;
+        return (
+          <>
+            {roleTags.map((a: any, i: number) => (
+              <span
+                style={{
+                  display: 'inline-flex',
+                  padding: '2px 5px',
+                  fontSize: 12,
+                  background: '#EEF3FA',
+                  borderRadius: 4,
+                  color: '#525866',
+                  marginRight: 4,
+                }}
+              >
+                {a.labelName}
+              </span>
+            ))}
+          </>
+        );
+      },
     },
     {
       title: '操作',
@@ -869,7 +885,7 @@ const RoleManage: FC<PageProps> = ({ roleManageModel: state, dispatch }) => {
   const tableDataSearchHandle = (paramName: string) => {
     set_tableDataFilterParams({
       ...tableDataFilterParams,
-      current:1,
+      current: 1,
       [`${paramName}`]: tableDataSearchKeywords,
     });
   };
@@ -932,7 +948,7 @@ const RoleManage: FC<PageProps> = ({ roleManageModel: state, dispatch }) => {
                 if (e.target.value.length == 0) {
                   set_tableDataFilterParams({
                     ...tableDataFilterParams,
-                    current:1,
+                    current: 1,
                     roleName: '',
                   });
                 }
@@ -955,7 +971,7 @@ const RoleManage: FC<PageProps> = ({ roleManageModel: state, dispatch }) => {
         newVer
         params={tableDataFilterParams}
         reload={reloadTable}
-        scroll={{y:`calc(100vh - 255px)`}}
+        scroll={{ y: `calc(100vh - 255px)` }}
         request={(params) => getData(params)}
       />
     </div>

+ 104 - 29
src/pages/platform/setting/roleManage/modals/modal.tsx

@@ -1,26 +1,44 @@
 import React from 'react';
 import KCModal from '@/components/KCModal';
 import KCProSelect from '@/components/KCProSelect';
-import { roleManageModelState, Dispatch } from 'umi';
+import { roleManageModelState, Dispatch, useModel } from 'umi';
 
 import { ProFormText, ProFormTextArea } from '@ant-design/pro-form';
 import { getHospList, getShareHospList } from '@/service/hospList';
-import { AddUsersDataType, getYoushuUsers } from '@/service/user';
+import { AddUsersDataType, getUserRelaSeletData, getYoushuUsers } from '@/service/user';
 
 import { TableActType } from '..';
 import { Form } from 'antd';
 import MenuEditer from '@/pages/platform/components/menuEditer/menu';
 import UserEditer from '@/pages/platform/components/usersEditer';
+import { getSysParamsByCode } from '@/service';
+import { KcimCenterSysId } from '@/constant';
+import { getDictByDictTypeAndSysid } from '@/service/dictionary';
 
 interface ActModalProps extends roleManageModelState {
   dispatch: Dispatch | undefined;
 }
 
-const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, currentEdit, hospId }) => {
+export function checkRoleTagsIsObjArr(roleTags: any[]) {
+  // 如果数组为空,直接返回 true
+  if (roleTags.length === 0) {
+    return true;
+  }
+
+  // 如果数组非空,则判断第一个元素是否为“非空且非数组的对象”
+  if (typeof roleTags[0] === 'object' && roleTags[0] !== null && !Array.isArray(roleTags[0])) {
+    return true;
+  }
+
+  return false;
+}
 
+const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, currentEdit, hospId }) => {
+  const { initialState, setInitialState } = useModel('@@initialState');
+  // const temp = {...currentEdit,roleTags:[{labelCode:'001',labelName:'后勤'}]}
   const setInitialValues = () => {
     if (tableAct === TableActType.EDIT) {
-      return { ...currentEdit };
+      return { ...currentEdit, dataPermission: currentEdit?.dataPermissionCode ?? null, roleTags: currentEdit?.roleTags?.map((a) => a.labelCode) ?? [] };
     }
     if (tableAct === TableActType.ADD) {
       return { hospId: hospId };
@@ -42,26 +60,44 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
   };
 
   const onFinishHandle = (data: any & AddUsersDataType) => {
-    console.log({initialValues});
-    
-
-    if (tableAct === TableActType.ADD) {
-      dispatch &&
-        dispatch({
-          type: 'roleManageModel/postAddData',
-          payload: data,  // 使用合并后的数据
-        });
+    try {
+      if (tableAct === TableActType.ADD) {
+        const { dataPermission, ...rest } = data;
+        dispatch &&
+          dispatch({
+            type: 'roleManageModel/postAddData',
+            payload: {
+              ...rest,
+              dataPermissionCode: data.dataPermission?.value ?? '',
+              dataPermissionName: data.dataPermission?.label ?? '',
+              roleTags: data.roleTags?.map((a: any) => ({ labelCode: a.value, labelName: a.label })),
+            }, // 使用合并后的数据
+          });
+      }
+
+      if (tableAct === TableActType.EDIT || tableAct === TableActType.EDITMENU || tableAct === TableActType.EDITRELAUSER) {
+        const { dataPermission, roleTags, ...rest } = data;
+        dispatch &&
+          dispatch({
+            type: 'roleManageModel/postEditData',
+            payload:
+              tableAct === TableActType.EDIT
+                ? {
+                    ...rest,
+                    dataPermissionCode: dataPermission && typeof dataPermission == 'string' ? data.dataPermissionCode : dataPermission?.value ?? '',
+                    dataPermissionName: dataPermission && typeof dataPermission == 'string' ? data.dataPermissionName : dataPermission?.label ?? '',
+                    roleTags: checkRoleTagsIsObjArr(roleTags) ? data.roleTags.map((a: any) => ({ labelCode: a.value, labelName: a.label })) : currentEdit?.roleTags,
+                  }
+                : {
+                    ...rest,
+                  },
+          });
+      }
+
+      return true;
+    } catch (error) {
+      console.log('add or edit error', error);
     }
-
-    if (tableAct === TableActType.EDIT || tableAct === TableActType.EDITMENU || tableAct === TableActType.EDITRELAUSER) {
-      dispatch &&
-        dispatch({
-          type: 'roleManageModel/postEditData',
-          payload: data,  // 使用合并后的数据
-        });
-    }
-
-    return true;
   };
 
   const setModalTitle = () => {
@@ -79,8 +115,6 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
     }
   };
 
-  
-
   return (
     <KCModal
       visible={isShowModal}
@@ -92,7 +126,7 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
       labelCol={{
         span: 5,
       }}
-      onFinish={async (data) => onFinishHandle({...data})}
+      onFinish={async (data) => onFinishHandle({ ...currentEdit, ...data })}
     >
       {tableAct === TableActType.EDITMENU && (
         <Form.Item name="bindMenuIds">
@@ -109,11 +143,11 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
       {(tableAct === TableActType.ADD || tableAct === TableActType.EDIT) && (
         <>
           <KCProSelect
-            label="选择院区"
+            label={initialState?.customerType == '2' ? '选择园区' : '选择院区'}
             width="md"
             name="hospId"
             disabled
-            fieldProps={{size:'small'}}
+            fieldProps={{ size: 'small' }}
             request={async () => {
               const hospList = await getShareHospList();
               if (hospList) {
@@ -137,7 +171,7 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
               },
             ]}
           />
-           <ProFormText
+          <ProFormText
             width="md"
             name="roleCode"
             label="角色编码"
@@ -153,6 +187,7 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
           <KCProSelect
             label="关联有数账号"
             width="md"
+            fieldProps={{ size: 'small' }}
             name="youshuUserId"
             request={async () => {
               const resp = await getYoushuUsers();
@@ -165,6 +200,46 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
               return [];
             }}
           />
+          <KCProSelect
+            label="数据权限"
+            width="md"
+            name="dataPermission"
+            fieldProps={{
+              size: 'small',
+              labelInValue: true,
+            }}
+            request={async () => {
+              // const resp = await getYoushuUsers();
+              const resp = await getDictByDictTypeAndSysid(KcimCenterSysId, 'ROLE_DATA_PERMISSIONS_DIC');
+              if (resp) {
+                return resp.dataVoList.map((t: any) => ({
+                  label: t.name,
+                  value: t.code,
+                }));
+              }
+              return [];
+            }}
+          />
+          <KCProSelect
+            label="角色标签"
+            width="md"
+            name="roleTags"
+            fieldProps={{
+              size: 'small',
+              labelInValue: true,
+              mode: 'tags',
+            }}
+            request={async () => {
+              const resp = await getDictByDictTypeAndSysid(KcimCenterSysId, 'ROLE_LABEL_DIC');
+              if (resp) {
+                return resp.dataVoList.map((t: any) => ({
+                  label: t.name,
+                  value: t.code,
+                }));
+              }
+              return [];
+            }}
+          />
 
           <ProFormTextArea name="remark" label="备注" placeholder="请输入名称" width="md" fieldProps={{}} />
         </>

+ 2 - 0
src/pages/platform/setting/roleManage/model.ts

@@ -17,6 +17,8 @@ import {
 } from '@/service/role';
 import { getMainHosp } from '@/service/hospList';
 import type { RoleItemType as TableListItem } from '@/service/role';
+import { getDictByDictTypeAndSysid } from '@/service/dictionary';
+import { KcimCenterSysId } from '@/constant';
 
 enum TableActType {
   NOACT,

+ 27 - 32
src/pages/platform/setting/systemNavMana/index.tsx

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2022-12-16 09:42:52
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-12-02 14:13:45
+ * @LastEditTime: 2025-02-18 15:11:01
  * @FilePath: /BudgetManaSystem/src/pages/budgetMana/monthlySet/index.tsx
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -35,12 +35,8 @@ import ProForm, { ModalForm, ProFormDependency, ProFormDigit, ProFormInstance, P
 import { KCInput } from '@/components/KCInput';
 import { UserRelaSeletDataListType } from '@/service/user';
 
-const IconFont = createFromIconfontCN({
-  scriptUrl: '//at.alicdn.com/t/c/font_1927152_4nm5kxbv4m3.js',
-});
-
 const SearchIcon = createFromIconfontCN({
-  scriptUrl: '//at.alicdn.com/t/c/font_1927152_g1njmm1kh7b.js',
+  scriptUrl: '',
 });
 
 export type TableListItem = {
@@ -162,21 +158,21 @@ const MonthlyInfoCheck: React.FC = () => {
       render: (_: any, record: any) => {
         return record.type != 1 && record.type != 2 && record.type != 4
           ? [
-            <UpDataActBtn key={'add'} record={record} type="ADD" />,
-            <UpDataActBtn key={'act'} record={record} type="EDIT" />,
-            <Popconfirm title="是否确认删除?" key="del" onConfirm={() => delMenuHandle(record)}>
-              <a>删除</a>
-            </Popconfirm>,
-          ]
+              <UpDataActBtn key={'add'} record={record} type="ADD" />,
+              <UpDataActBtn key={'act'} record={record} type="EDIT" />,
+              <Popconfirm title="是否确认删除?" key="del" onConfirm={() => delMenuHandle(record)}>
+                <a>删除</a>
+              </Popconfirm>,
+            ]
           : [
-            <a key={'fuc'} onClick={() => addFuncHandle(record)}>
-              功能
-            </a>,
-            <UpDataActBtn key={'act'} record={record} type="EDIT" />,
-            <Popconfirm title="是否确认删除?" key="del" onConfirm={() => delMenuHandle(record)}>
-              <a>删除</a>
-            </Popconfirm>,
-          ];
+              <a key={'fuc'} onClick={() => addFuncHandle(record)}>
+                功能
+              </a>,
+              <UpDataActBtn key={'act'} record={record} type="EDIT" />,
+              <Popconfirm title="是否确认删除?" key="del" onConfirm={() => delMenuHandle(record)}>
+                <a>删除</a>
+              </Popconfirm>,
+            ];
       },
     },
   ];
@@ -415,7 +411,7 @@ const MonthlyInfoCheck: React.FC = () => {
   const tableDataSearchHandle = (paramName: string) => {
     set_tableDataFilterParams({
       ...tableDataFilterParams,
-      current:1,
+      current: 1,
       [`${paramName}`]: tableDataSearchKeywords,
     });
   };
@@ -469,7 +465,7 @@ const MonthlyInfoCheck: React.FC = () => {
           name="type"
           label="类型:"
           placeholder="请输入"
-          fieldProps={{size:'small'}}
+          fieldProps={{ size: 'small' }}
           colProps={{ span: 12 }}
           options={[
             {
@@ -489,7 +485,7 @@ const MonthlyInfoCheck: React.FC = () => {
           label="icon:"
           placeholder="请输入"
           colProps={{ span: 12 }}
-        //rules={[{ required: true, message: '类型代码不能为空!' }]}
+          //rules={[{ required: true, message: '类型代码不能为空!' }]}
         />
         <ProFormDigit
           name="orderNum"
@@ -497,7 +493,7 @@ const MonthlyInfoCheck: React.FC = () => {
           width={'xs'}
           placeholder="请输入"
           colProps={{ span: 12 }}
-        //rules={[{ required: true, message: '类型代码不能为空!' }]}
+          //rules={[{ required: true, message: '类型代码不能为空!' }]}
         />
         <ProFormDependency name={['type']}>
           {({ type }) =>
@@ -519,7 +515,7 @@ const MonthlyInfoCheck: React.FC = () => {
                     value: 0,
                   },
                 ]}
-              //rules={[{ required: true, message: '类型代码不能为空!' }]}
+                //rules={[{ required: true, message: '类型代码不能为空!' }]}
               />
             )
           }
@@ -534,7 +530,6 @@ const MonthlyInfoCheck: React.FC = () => {
                   label="内容类型:"
                   placeholder="请输入"
                   width={100}
-
                   colProps={{ span: 8 }}
                   request={async () => {
                     if (contentType) {
@@ -543,7 +538,7 @@ const MonthlyInfoCheck: React.FC = () => {
                     return [];
                   }}
                   fieldProps={{
-                    size:'small',
+                    size: 'small',
                     onChange(value, option) {
                       if (value == 1) {
                         //报告页面
@@ -574,7 +569,7 @@ const MonthlyInfoCheck: React.FC = () => {
                             name="reportId"
                             placeholder="请输入报告Id"
                             colProps={{ span: 16 }}
-                          //rules={[{ required: true, message: '类型代码不能为空!' }]}
+                            //rules={[{ required: true, message: '类型代码不能为空!' }]}
                           />
                         </div>
                       )
@@ -594,7 +589,7 @@ const MonthlyInfoCheck: React.FC = () => {
                 label="URL:"
                 placeholder="请输入"
                 colProps={{ span: 12 }}
-              //rules={[{ required: true, message: '类型代码不能为空!' }]}
+                //rules={[{ required: true, message: '类型代码不能为空!' }]}
               />
             )
           }
@@ -607,7 +602,7 @@ const MonthlyInfoCheck: React.FC = () => {
 
   useEffect(() => {
     if (currentSelectedTreeNode) {
-      set_tableDataFilterParams({ ...tableDataFilterParams,current:1, systemId: currentSelectedTreeNode.code });
+      set_tableDataFilterParams({ ...tableDataFilterParams, current: 1, systemId: currentSelectedTreeNode.code });
     }
   }, [currentSelectedTreeNode]);
 
@@ -635,7 +630,7 @@ const MonthlyInfoCheck: React.FC = () => {
         <div className="search">
           <Input className="searchInput" placeholder="请输入" size="small" allowClear onChange={onTreeSearchKeyChange} suffix={<SearchIcon type="iconsousuo" />} />
         </div>
-        <div className='treeeWrap'>
+        <div className="treeeWrap">
           {treeData && treeData.length > 0 && (
             <DirectoryTree
               fieldNames={{ title: 'name', key: 'code' }}
@@ -716,7 +711,7 @@ const MonthlyInfoCheck: React.FC = () => {
                   if (e.target.value.length == 0) {
                     set_tableDataFilterParams({
                       ...tableDataFilterParams,
-                      current:1,
+                      current: 1,
                       name: '',
                     });
                   }

+ 85 - 38
src/pages/platform/setting/userManage/index.tsx

@@ -1,15 +1,15 @@
 /*
  * @Author: your name
  * @Date: 2022-01-11 09:43:18
- * @LastEditTime: 2024-12-02 14:15:06
+ * @LastEditTime: 2025-01-14 10:50:18
  * @LastEditors: code4eat awesomedema@gmail.com
  * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/userManage/index.tsx
  */
 
 import { FC, useEffect, useState } from 'react';
-import { userManageModelState, ConnectProps, Loading, connect } from 'umi';
-import { Button, Divider, Popconfirm } from 'antd';
+import { userManageModelState, ConnectProps, Loading, connect, useModel } from 'umi';
+import { Button, Divider, Popconfirm, Popover, Tooltip } from 'antd';
 import KCTable from '@/components/kcTable';
 import type { ProColumns } from '@ant-design/pro-table';
 import { getUserRelaSeletData, getUsers, UserRelaSeletDataListType, UserRelaSeletDataType } from '@/service/user';
@@ -38,7 +38,7 @@ interface PageProps extends ConnectProps {
 
 const UserManage: FC<PageProps> = ({ userManageModel: state, dispatch }) => {
   const { reloadTable } = state;
-
+  const { initialState, setInitialState } = useModel('@@initialState');
   const [tableDataFilterParams, set_tableDataFilterParams] = useState<any | undefined>();
   const [tableDataSearchKeywords, set_tableDataSearchKeywords] = useState<string>('');
 
@@ -46,10 +46,10 @@ const UserManage: FC<PageProps> = ({ userManageModel: state, dispatch }) => {
 
   const columns: ProColumns<TableListItem>[] = [
     {
-      title: '院区',
+      title: initialState?.customerType == '2' ? '园区' : '院区',
       dataIndex: 'hospName',
-      ellipsis:true,
-      width:210
+      ellipsis: true,
+      width: 210,
     },
     {
       title: '姓名',
@@ -62,7 +62,7 @@ const UserManage: FC<PageProps> = ({ userManageModel: state, dispatch }) => {
       hideInSearch: false,
     },
     {
-      title: '电话',
+      title: '联系电话',
       dataIndex: 'phoneNumber',
     },
     {
@@ -77,31 +77,78 @@ const UserManage: FC<PageProps> = ({ userManageModel: state, dispatch }) => {
         1: { text: '是', status: 'Success' },
       },
     },
+    {
+      title: initialState?.customerType == '2' ? '人员标签' : '人员标签',
+      width: 160,
+      ellipsis: true,
+      renderText(text: any, record: any) {
+        const { tagList = [] } = record;
+        return (
+          <>
+            {tagList.map((a: any, i: number) => (
+              <span
+                style={{
+                  display: 'inline-flex',
+                  padding: '2px 5px',
+                  fontSize: 12,
+                  background: '#EEF3FA',
+                  borderRadius: 4,
+                  color: '#525866',
+                  marginRight: 4,
+                }}
+              >
+                {a.tagName}
+              </span>
+            ))}
+          </>
+        );
+      },
+    },
     {
       title: '操作',
-      width: 220,
+      width: 180,
       key: 'option',
       valueType: 'option',
-      render: (text, record) => [
-        <a key="link" onClick={() => editHandle(record)}>
-          编辑
-        </a>,
-        <Divider key="1" type="vertical" style={{ margin: '0 3px' }} />,
-        <Popconfirm
-          title="是否确定删除?"
-          onConfirm={() => delHandle(record)}
-          // onCancel={cancel}
-          okText="确定"
-          cancelText="取消"
-          key="link2"
-        >
-          <a>删除</a>
-        </Popconfirm>,
-        <Divider key="2" type="vertical" style={{ margin: '0 3px' }} />,
-        <a key="link3" onClick={() => resetUserPaswdHandle(record)}>
-          重置密码
-        </a>,
-      ],
+      render: (text, record) => {
+        const getEditBtn = () => {
+          const { isEdit } = record;
+          if (!isEdit) {
+            return [
+              <a key="link" onClick={() => editHandle(record)}>
+                编辑
+              </a>,
+              <Divider key="1" type="vertical" style={{ margin: '0 3px' }} />,
+              <Popconfirm
+                title="是否确定删除?"
+                onConfirm={() => delHandle(record)}
+                // onCancel={cancel}
+                okText="确定"
+                cancelText="取消"
+                key="link2"
+              >
+                <a>删除</a>
+              </Popconfirm>,
+            ];
+          } else {
+            return [
+              <Tooltip title="第三方人员不可编辑!">
+                <span style={{ cursor: 'not-allowed', color: 'rgb(176 181 189)' }}>编辑</span>
+              </Tooltip>,
+              <Divider key="1" type="vertical" style={{ margin: '0 3px' }} />,
+              <Tooltip title="第三方人员不可编辑!">
+                <span style={{ cursor: 'not-allowed', color: 'rgb(176 181 189)' }}>删除</span>
+              </Tooltip>,
+            ];
+          }
+        };
+        return [
+          ...getEditBtn(),
+          <Divider key="2" type="vertical" style={{ margin: '0 3px' }} />,
+          <a key="link3" onClick={() => resetUserPaswdHandle(record)}>
+            重置密码
+          </a>,
+        ];
+      },
     },
   ];
 
@@ -197,7 +244,7 @@ const UserManage: FC<PageProps> = ({ userManageModel: state, dispatch }) => {
   const tableDataSearchHandle = (paramName: string) => {
     set_tableDataFilterParams({
       ...tableDataFilterParams,
-      current:1,
+      current: 1,
       [`${paramName}`]: tableDataSearchKeywords,
     });
   };
@@ -271,7 +318,7 @@ const UserManage: FC<PageProps> = ({ userManageModel: state, dispatch }) => {
       <div className="toolBar">
         <div className="filter">
           <div className="filterItem" style={{ marginRight: 16 }}>
-            <span className="label">科室:</span>
+            <span className="label">{initialState?.customerType == '2' ? '组织:' : '科室:'}</span>
             <ProFormSelect
               name="departmentId"
               noStyle
@@ -285,12 +332,12 @@ const UserManage: FC<PageProps> = ({ userManageModel: state, dispatch }) => {
                 return [];
               }}
               fieldProps={{
-                size:'small',
+                size: 'small',
                 showSearch: true,
                 onChange: (val) => {
                   set_tableDataFilterParams({
                     ...tableDataFilterParams,
-                    current:1,
+                    current: 1,
                     departmentId: val,
                   });
                 },
@@ -309,7 +356,7 @@ const UserManage: FC<PageProps> = ({ userManageModel: state, dispatch }) => {
                 if (e.target.value.length == 0) {
                   set_tableDataFilterParams({
                     ...tableDataFilterParams,
-                    current:1,
+                    current: 1,
                     name: '',
                   });
                 }
@@ -333,15 +380,15 @@ const UserManage: FC<PageProps> = ({ userManageModel: state, dispatch }) => {
         reload={reloadTable}
         params={tableDataFilterParams}
         newVer
-        scroll={{y:`calc(100vh - 255px)`}}
+        scroll={{ y: `calc(100vh - 255px)` }}
         expandable={{
           expandedRowRender: (record) => (
             <div className="userExpandInfo">
-              <span>进院时间:{record.entryTime}</span>
-              <span>手机号:{record.phoneNumber}</span>
+              {initialState?.customerType == '2' ? <span>入职时间:{record.entryTime}</span> : <span>入职时间:{record.entryTime}</span>}
+              {initialState?.customerType == '2' ? <span>联系电话:{record.phoneNumber}</span> : <span>联系电话:{record.phoneNumber}</span>}
               <span>所学专业:{record.major}</span>
               <span>资格证号:{record.qualificationCertificateNo}</span>
-              <span>医师级别:{record.doctorLevel}</span>
+              {initialState?.customerType == '2' ? <span>执业级别:{record.doctorLevel}</span> : <span>医师级别:{record.doctorLevel}</span>}
               <span>执业证号:{record.practiceCertificateNo}</span>
               <span>执业类别:{record.practiceSubject}</span>
               <span>执业科目:{record.practiceCate}</span>

+ 113 - 64
src/pages/platform/setting/userManage/modal.tsx

@@ -1,7 +1,7 @@
 /*
  * @Author: your name
  * @Date: 2022-01-12 17:11:11
- * @LastEditTime: 2024-11-26 17:59:43
+ * @LastEditTime: 2025-02-24 10:52:46
  * @LastEditors: code4eat awesomedema@gmail.com
  * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/userManage/modal.tsx
@@ -11,7 +11,7 @@ import React, { useEffect, useState } from 'react';
 import KCModal from '@/components/KCModal';
 import KCProSelect from '@/components/KCProSelect';
 import { message, Button, Spin, Upload, UploadProps } from 'antd';
-import { userManageModelState, Dispatch } from 'umi';
+import { userManageModelState, Dispatch, useModel } from 'umi';
 import { ProFormDateTimePicker, ProFormDependency, ProFormRadio, ProFormSelect, ProFormText, ProFormTextArea } from '@ant-design/pro-form';
 import { getShareHospList } from '@/service/hospList';
 import { AddUsersDataType, getUserRelaSeletData, uploadAvatar, UserRelaSeletDataListType, UserRelaSeletDataType } from '@/service/user';
@@ -21,6 +21,9 @@ import { LoadingOutlined, PlusOutlined, UploadOutlined } from '@ant-design/icons
 import './style.less';
 import { getDepartmentData } from '../departmentMana/service';
 import { getSysParamsByCode } from '@/service';
+import { getDictByDictTypeAndSysid } from '@/service/dictionary';
+import { KcimCenterSysId } from '@/constant';
+import { checkRoleTagsIsObjArr } from '../roleManage/modals/modal';
 
 export enum TableActType {
   NOACT,
@@ -36,7 +39,7 @@ interface ActModalProps extends userManageModelState {
 const antIcon = <LoadingOutlined style={{ fontSize: 24 }} spin />;
 
 const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, currentEditUser }) => {
-
+  const { initialState, setInitialState } = useModel('@@initialState');
   const [avatarUrl, setAvatarUrl] = useState<string>('');
   const [loadAvatar, setLoadAvatar] = useState(false);
   const [ifCheckPhoneNumber, set_ifCheckPhoneNumber] = useState(false);
@@ -52,7 +55,6 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
       setLoadAvatar(true);
 
       if (info.file.status !== 'uploading') {
-
         const form = new FormData();
         form.append('file', info.file.originFileObj);
         const resp = await uploadAvatar(form);
@@ -87,22 +89,30 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
   };
 
   const onFinishHandle = (data: any & AddUsersDataType) => {
-    // console.log({ data });
-    if (tableAct == TableActType.ADD) {
-      dispatch &&
-        dispatch({
-          type: 'userManageModel/postAddUserData',
-          payload: { ...data, avatarUrl: avatarUrl },
-        });
-    }
+    try {
+      if (tableAct == TableActType.ADD) {
+        dispatch &&
+          dispatch({
+            type: 'userManageModel/postAddUserData',
+            payload: { ...data, avatarUrl: avatarUrl, tagList: data.tagList?.map((a: any) => ({ tagCode: a.value, tagName: a.label })) },
+          });
+      }
 
-    if (tableAct == TableActType.EDIT) {
-      dispatch &&
-        dispatch({
-          type: 'userManageModel/postEditUserData',
-          payload: { ...data, avatarUrl: avatarUrl },
-        });
+      if (tableAct == TableActType.EDIT) {
+        dispatch &&
+          dispatch({
+            type: 'userManageModel/postEditUserData',
+            payload: {
+              ...data,
+              avatarUrl: avatarUrl,
+              tagList: checkRoleTagsIsObjArr(data.tagList) ? data.tagList.map((a: any) => ({ tagCode: a.value, tagName: a.label })) : currentEditUser?.tagList,
+            },
+          });
+      }
+    } catch (error) {
+      console.log('onFinishHandleError', error);
     }
+    // console.log({ data });
 
     return true;
   };
@@ -122,7 +132,7 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
         set_ifCheckPhoneNumber(needItem[0].value == '0' ? false : true);
       }
     }
-  }
+  };
 
   const setSelectorData = (key: string) => {
     let result = dirData.filter((t: any) => t.code == key);
@@ -174,21 +184,21 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
       className="ProfileModal"
       initialValues={
         tableAct == TableActType.EDIT
-          ? { ...currentEditUser, departmentId: { label: currentEditUser?.departmentName, value: currentEditUser?.departmentId } }
+          ? { ...currentEditUser, departmentId: { label: currentEditUser?.departmentName, value: currentEditUser?.departmentId }, tagList: currentEditUser?.tagList?.map((a: any) => a.tagCode) ?? [] }
           : {
-            //设置当新增时的默认值
-            major: setSelectorData('PROFESSIONAL_TYPE').defaultvalue,
-            jobTitle: setSelectorData('POSITION_TYPE').defaultvalue,
-            doctorLevel: setSelectorData('PHYSICIAN_TYPE').defaultvalue,
-            practiceSubject: setSelectorData('PROCESSIONAL_SUBJECTS_TYPE').defaultvalue,
-            title: setSelectorData('TITLE_TYPE').defaultvalue,
-            practiceCate: setSelectorData('JOB_TYPE').defaultvalue,
-            practiceStatus: setSelectorData('PROFESSIONAL_STATUS_TYPE').defaultvalue,
-            position: setSelectorData('POSITION').defaultvalue,
-            departmentId: setSelectorData('PRACTICE_DEPARTMENT_TYPE').defaultvalue,
-            userCate: setSelectorData('PERSONNEL_CATEGORY').defaultvalue,
-            degree: setSelectorData('EDUCATION').defaultvalue,
-          }
+              //设置当新增时的默认值
+              major: setSelectorData('PROFESSIONAL_TYPE').defaultvalue,
+              jobTitle: setSelectorData('POSITION_TYPE').defaultvalue,
+              doctorLevel: setSelectorData('PHYSICIAN_TYPE').defaultvalue,
+              practiceSubject: setSelectorData('PROCESSIONAL_SUBJECTS_TYPE').defaultvalue,
+              title: setSelectorData('TITLE_TYPE').defaultvalue,
+              practiceCate: setSelectorData('JOB_TYPE').defaultvalue,
+              practiceStatus: setSelectorData('PROFESSIONAL_STATUS_TYPE').defaultvalue,
+              position: setSelectorData('POSITION').defaultvalue,
+              departmentId: setSelectorData('PRACTICE_DEPARTMENT_TYPE').defaultvalue,
+              userCate: setSelectorData('PERSONNEL_CATEGORY').defaultvalue,
+              degree: setSelectorData('EDUCATION').defaultvalue,
+            }
       }
       title={tableAct == TableActType.EDIT ? '编辑用户' : '新增用户'}
       // labelCol={{ span: 5 }}
@@ -262,7 +272,7 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
               </div>
               <div className="formItem">
                 <KCProSelect
-                  label="所属院区:"
+                  label={initialState?.customerType == '2' ? '所属园区:' : '所属院区:'}
                   width="md"
                   name="hospId"
                   fieldProps={{ showSearch: true, size: 'small' }}
@@ -279,7 +289,7 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
                   rules={[
                     {
                       required: true,
-                      message: '请选择院区!',
+                      message: initialState?.customerType == '2' ? '请选择园区!' : '请选择院区!',
                     },
                   ]}
                 />
@@ -287,7 +297,7 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
               <div className="formItem">
                 <ProFormSelect
                   name="departmentId"
-                  label="科室:"
+                  label={initialState?.customerType == '2' ? '组织:' : '科室:'}
                   request={async () => {
                     const resp = await getDepartmentData({ pageSize: 1000 });
                     if (resp) {
@@ -304,7 +314,13 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
                 />
               </div>
               <div className="formItem">
-                <ProFormSelect name="userCate" label="人员类别:" fieldProps={{ showSearch: true, size: 'small' }} options={setSelectorData('PERSONNEL_CATEGORY').list} placeholder="请选择" />
+                <ProFormSelect
+                  name="userCate"
+                  label={initialState?.customerType == '2' ? '员工类别:' : '人员类别:'}
+                  fieldProps={{ showSearch: true, size: 'small' }}
+                  options={setSelectorData('PERSONNEL_CATEGORY').list}
+                  placeholder="请选择"
+                />
               </div>
 
               <div className="formItem">
@@ -371,31 +387,35 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
               <div className="formItem">
                 <ProFormText
                   name="phoneNumber"
-                  label="手机号:"
+                  label={initialState?.customerType == '2' ? '联系电话:' : '联系电话:'}
                   placeholder="请输入"
-                  rules={ifCheckPhoneNumber?[
-                    {
-                      required:true,
-                      message: <span style={{fontSize:12,paddingLeft:50}}>请输入手机号</span>,
-                    },
-                    {
-                      pattern: /^1[3-9]\d{9}$/,
-                      message:<span style={{fontSize:12,paddingLeft:50}}>手机号格式不正确</span>,
-                    },
-                    // ({ getFieldValue }) => ({
-                    //   validator(_, value) {
-                    //     if (!value || value.length === 11) {
-                    //       return Promise.resolve();
-                    //     }
-                    //     return Promise.reject(new Error("手机号必须是11位"));
-                    //   },
-                    // }),
-                  ]:[
-                    {
-                      required:false,
-                      message: "请输入手机号",
-                    },
-                  ]}
+                  rules={
+                    ifCheckPhoneNumber
+                      ? [
+                          {
+                            required: true,
+                            message: <span style={{ fontSize: 12, paddingLeft: 50 }}>请输入联系电话</span>,
+                          },
+                          {
+                            pattern: /^1[3-9]\d{9}$/,
+                            message: <span style={{ fontSize: 12, paddingLeft: 50 }}>手机号格式不正确</span>,
+                          },
+                          // ({ getFieldValue }) => ({
+                          //   validator(_, value) {
+                          //     if (!value || value.length === 11) {
+                          //       return Promise.resolve();
+                          //     }
+                          //     return Promise.reject(new Error("手机号必须是11位"));
+                          //   },
+                          // }),
+                        ]
+                      : [
+                          {
+                            required: false,
+                            message: '请输入',
+                          },
+                        ]
+                  }
                 />
               </div>
 
@@ -469,7 +489,7 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
                     <div className="formItem" style={{ position: 'relative', top: -62 }}>
                       <ProFormSelect
                         name="doctorLevel"
-                        label="医师:"
+                        label={initialState?.customerType == '2' ? '执业级别:' : '医师级别:'}
                         fieldProps={{ showSearch: true, size: 'small' }}
                         options={setSelectorData('PHYSICIAN_TYPE').list}
                         placeholder="请选择"
@@ -482,8 +502,37 @@ const ActModal: React.FC<ActModalProps> = ({ dispatch, isShowModal, tableAct, cu
             </div>
           </div>
 
-          <div style={{ display: 'flex', flexDirection: 'row', width: '100%' }}>
-            <ProFormTextArea label="备注" name="remark" width={688} placeholder="请输入备注" />
+          <div style={{ display: 'flex', flexDirection: 'row', width: '100%', justifyContent: 'space-between', alignItems: 'center' }}>
+            <KCProSelect
+              label={initialState?.customerType == '2' ? '人员标签:' : '人员标签:'}
+              width={160}
+              name="tagList"
+              fieldProps={{
+                size: 'small',
+                labelInValue: true,
+                mode: 'tags',
+                maxTagCount: 'responsive',
+              }}
+              request={async () => {
+                const resp = await getDictByDictTypeAndSysid(KcimCenterSysId, 'EMPLOYEE_LABEL_DIC');
+                if (resp) {
+                  return resp.dataVoList.map((t: any) => ({
+                    label: t.name,
+                    value: t.code,
+                  }));
+                }
+                return [];
+              }}
+            />
+            <ProFormTextArea
+              fieldProps={{
+                style: { border: '1px solid #cfd7e6', marginTop: '-5px' },
+              }}
+              label="备注"
+              name="remark"
+              width={512}
+              placeholder="请输入备注"
+            />
           </div>
         </div>
       ) : (

+ 17 - 0
src/service/ai.ts

@@ -0,0 +1,17 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2025-03-03 11:02:52
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-03-10 11:09:03
+ * @FilePath: /KC-MiddlePlatform/src/service/ai.ts
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+import { request } from 'umi';
+
+export const askAiReq = async (data: any) => {
+  return request('/ask', {
+    method: 'POST',
+    data: data,
+    prefix: '/ai',
+  });
+};

+ 9 - 1
src/service/dictionary.ts

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2022-07-06 11:46:24
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2023-01-10 13:16:27
+ * @LastEditTime: 2025-02-21 14:14:47
  * @FilePath: /KC-MiddlePlatform/src/service/dictionary.ts
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -40,8 +40,16 @@ export type IndicatorDictionaryDataType = {
   code: string;
   children: IndicatorDictionaryDataType[];
 };
+
 export const getIndicatorDictionary = async () => {
   return request<IndicatorDictionaryDataType[]>('/centerSys/indicator/getDict', {
     method: 'GET',
   });
 };
+
+export const getDictByDictTypeAndSysid = async (systemId: string, dictType: string) => {
+  return request('/centerSys/sysdictdata/getDictByDictType', {
+    method: 'GET',
+    params: { dictType, systemId },
+  });
+};

+ 17 - 1
src/service/index.ts

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2022-06-27 15:43:25
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-11-26 17:30:33
+ * @LastEditTime: 2025-01-09 10:36:34
  * @FilePath: /KC-MiddlePlatform/src/service/index.ts
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -79,3 +79,19 @@ export const getAppAccess = async (systemId: any) => {
     params: { systemId },
   });
 };
+
+//删除快速入口
+
+export const removeFastEntrance = async (ids: string[]) => {
+  return request('/centerSys/index/removeFastEntrance', {
+    method: 'POST',
+    data: ids,
+  });
+};
+//切换组织
+export const switchOrgReq = async (orgId: string, orgName: string) => {
+  return request('/centerSys/index/switchOrg', {
+    method: 'POST',
+    params: { orgId, orgName },
+  });
+};

+ 61 - 28
src/service/indicator.ts

@@ -2,67 +2,100 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2022-07-15 10:13:10
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2023-01-10 13:16:01
+ * @LastEditTime: 2025-03-07 13:42:09
  * @FilePath: /KC-MiddlePlatform/src/service/indicator.ts
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
 
-
 import { request } from 'umi';
 
 //获取指标列表
 
 export type IndicatorManaItemType = {
-    [x: string]: any;
-    current: number;
-    pageSize: number;
-    totalCount: number;
-    totalPage: number;
-    list?: any[];
-}
-export const getIndicatorManaList = async (params: {
-    current: number;
-    pageSize: number;
-    menuCode:string;
-    [key:string]:any;
-  }) => {
-    return request<IndicatorManaItemType>('/centerSys/indicator/getIndicatorList', {
-      method: 'GET',
-      params:{...params,pageSize:100}
-    });
+  [x: string]: any;
+  current: number;
+  pageSize: number;
+  totalCount: number;
+  totalPage: number;
+  list?: any[];
+};
+export const getIndicatorManaList = async (params: { current?: number; pageSize?: number; menuCode: string; menuType: string; [key: string]: any }) => {
+  return request<IndicatorManaItemType>('/centerSys/indicator/getIndicatorDetail', {
+    method: 'GET',
+    params: { ...params, pageSize: 100 },
+  });
 };
 
 //添加指标信息
-export const addIndicatorManaList = async (data:any) => {
+export const addIndicatorManaList = async (data: any) => {
   return request('/centerSys/indicator/addIndicator', {
     method: 'POST',
-    data:data
+    data: data,
   });
 };
 
 //编辑指标信息
-export const editIndicatorManaList = async (data:any) => {
+export const editIndicatorManaList = async (data: any) => {
   return request('/centerSys/indicator/editIndicator', {
     method: 'POST',
-    data:data
+    data: data,
   });
 };
 
-
+//保存指标与数据项目的绑定
+export const saveIndicatorBindItems = async (data: any) => {
+  return request('/centerSys/indicator/applyIndicatorDataMap', {
+    method: 'POST',
+    data: data,
+  });
+};
 
 //删除指标
-export const delIndicatorManaList = async (params:{id:number}) => {
+export const delIndicatorManaList = async (params: { id: number }) => {
   return request('/centerSys/indicator/deleteIndicator', {
     method: 'POST',
-    params:params
+    params: params,
   });
 };
 
 //获取指标目录
 
-export const getIndicatorCateList = async (params?:{menuCode:string}) => {
+export const getIndicatorCateList = async (params?: { menuType: string }) => {
+  return request<any[]>('/centerSys/indicator/getHospIndicatorMenu', {
+    method: 'GET',
+    params: params,
+  });
+};
+
+//获取指标穿梭框数据
+export const getTransferDataReq = async (indicatorCode: string) => {
+  return request('/centerSys/indicator/getIndicatorDataMap', {
+    method: 'GET',
+    params: { indicatorCode },
+  });
+};
+
+//获取指标详情
+export const getIndicatorDetailReq = async (params: { indicatorCode: string; dataYear: string; periodType: string; fillItemName?: string }) => {
+  return request('/centerSys/indicator/getIndicatorFillData', {
+    method: 'GET',
+    params: params,
+  });
+};
+
+//老版本
+export const getIndicatorManaList_old = async (params: { current: number; pageSize: number; menuCode: string; [key: string]: any }) => {
+  return request<IndicatorManaItemType>('/centerSys/indicator/getIndicatorList', {
+    method: 'GET',
+    params: { ...params, pageSize: 100 },
+  });
+};
+
+//获取指标目录 老版本
+
+export const getIndicatorCateList_old = async (params?: { menuCode: string }) => {
   return request<any[]>('/centerSys/indicator/getIndicatorMenu', {
     method: 'GET',
-    params:params
+    params: params,
   });
 };

+ 4 - 1
src/service/role.ts

@@ -1,7 +1,7 @@
 /*
  * @Author: your name
  * @Date: 2022-01-18 14:56:29
- * @LastEditTime: 2023-09-15 15:01:04
+ * @LastEditTime: 2024-12-27 14:43:38
  * @LastEditors: code4eat awesomedema@gmail.com
  * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  * @FilePath: /KC-MiddlePlatform/src/service/role.ts
@@ -20,6 +20,9 @@ export type RoleItemType = {
   modifyTime: string;
   remark: string;
   hospId: number;
+  dataPermissionCode?:string;
+  dataPermissionName?:string;
+  roleTags?:any[]
 };
 
 type GetAllRolesType = { list: RoleItemType[] } & TableResponseDataType;

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio