Prechádzať zdrojové kódy

Merge branch 'master' of http://120.55.44.4:10080/xujunjie/WorkloadWeb

Renxy 2 rokov pred
rodič
commit
50a466bcdc
66 zmenil súbory, kde vykonal 4338 pridanie a 1999 odobranie
  1. 12 11
      config/config.js
  2. 16 0
      config/router.config.js
  3. 4 2
      package.json
  4. 36 34
      public/luckysheet.html
  5. 2 1
      src/components/Flow/config-toolbar.ts
  6. 109 5
      src/components/Flow/index.tsx
  7. 3 1
      src/components/Flow/node/circle/index.tsx
  8. 2 0
      src/components/Flow/node/circle/mapServe.tsx
  9. 15 33
      src/components/Flow/node/control-map-service/components/edge.tsx
  10. 1 1
      src/components/Flow/node/fields/radio.tsx
  11. 67 10
      src/components/Flow/node/rect/mapServe.tsx
  12. 21 3
      src/components/Flow/node/registerNode.tsx
  13. 43 43
      src/global.less
  14. 80 5
      src/models/user.js
  15. 1 0
      src/models/xflow.js
  16. 116 120
      src/pages/PurchaseAdmin/PurchaseList/Approval/ApprovalModal.js
  17. 77 71
      src/pages/PurchaseAdmin/PurchaseList/Approval/Auth.js
  18. 18 9
      src/pages/PurchaseAdmin/PurchaseList/Approval/AuthModal.js
  19. 102 24
      src/pages/PurchaseAdmin/PurchaseList/Approval/DetailModal.js
  20. 35 74
      src/pages/PurchaseAdmin/PurchaseList/Approval/ExecutionModal.js
  21. 113 64
      src/pages/PurchaseAdmin/PurchaseList/Approval/List.js
  22. 33 81
      src/pages/PurchaseAdmin/PurchaseList/Approval/MemberModal.js
  23. 39 75
      src/pages/PurchaseAdmin/PurchaseList/Approval/QualityOperateModal.js
  24. 24 7
      src/pages/PurchaseAdmin/PurchaseList/Approval/models/approval.js
  25. 502 72
      src/pages/PurchaseAdmin/PurchaseList/Detail/CommitAuditModal.js
  26. 24 26
      src/pages/PurchaseAdmin/PurchaseList/Detail/ExportModal.js
  27. 82 65
      src/pages/PurchaseAdmin/PurchaseList/Detail/FilesModal.js
  28. 284 62
      src/pages/PurchaseAdmin/PurchaseList/Detail/FlowModal.js
  29. 267 62
      src/pages/PurchaseAdmin/PurchaseList/Detail/Index.js
  30. 10 0
      src/pages/PurchaseAdmin/PurchaseList/Detail/Index.less
  31. 24 15
      src/pages/PurchaseAdmin/PurchaseList/Detail/LuckySheet.js
  32. 8 4
      src/pages/PurchaseAdmin/PurchaseList/Detail/TimeNode.js
  33. 76 2
      src/pages/PurchaseAdmin/PurchaseList/Detail/models/detail.js
  34. 85 213
      src/pages/PurchaseAdmin/PurchaseList/Flow/Audit.js
  35. 8 11
      src/pages/PurchaseAdmin/PurchaseList/Flow/AuditList.js
  36. 3 0
      src/pages/PurchaseAdmin/PurchaseList/Flow/Flow.js
  37. 626 224
      src/pages/PurchaseAdmin/PurchaseList/Flow/FlowDetail.json
  38. 7 0
      src/pages/PurchaseAdmin/PurchaseList/Flow/List.js
  39. 168 0
      src/pages/PurchaseAdmin/PurchaseList/Flow/models/flow.js
  40. 32 18
      src/pages/PurchaseAdmin/PurchaseList/Index.js
  41. 104 6
      src/pages/PurchaseAdmin/PurchaseList/List/NewList.js
  42. 16 4
      src/pages/PurchaseAdmin/PurchaseList/List/models/list.js
  43. 46 3
      src/pages/PurchaseAdmin/PurchaseList/List/models/newList.js
  44. 31 41
      src/pages/PurchaseAdmin/PurchaseList/Report/Department.js
  45. 137 112
      src/pages/PurchaseAdmin/PurchaseList/Report/Project.js
  46. 1 3
      src/pages/PurchaseAdmin/PurchaseList/Report/Resource.js
  47. 55 21
      src/pages/PurchaseAdmin/PurchaseList/Report/UserProjectRptModal.js
  48. 2 2
      src/pages/PurchaseAdmin/PurchaseList/Report/UserRptModal.js
  49. 149 54
      src/pages/PurchaseAdmin/PurchaseList/Report/models/report.js
  50. 101 94
      src/pages/PurchaseAdmin/PurchaseList/WorkingHours/AddModal.js
  51. 59 14
      src/pages/PurchaseAdmin/PurchaseList/WorkingHours/Auth.js
  52. 32 52
      src/pages/PurchaseAdmin/PurchaseList/WorkingHours/AuthWorkList.js
  53. 13 26
      src/pages/PurchaseAdmin/PurchaseList/WorkingHours/CalendarModal.js
  54. 9 10
      src/pages/PurchaseAdmin/PurchaseList/WorkingHours/RejectModal.js
  55. 1 3
      src/pages/PurchaseAdmin/PurchaseList/WorkingHours/SearchForm.js
  56. 30 16
      src/pages/PurchaseAdmin/PurchaseList/WorkingHours/WorkList.js
  57. 18 32
      src/pages/PurchaseAdmin/PurchaseList/WorkingHours/index.js
  58. 26 21
      src/pages/PurchaseAdmin/PurchaseList/WorkingHours/models/workingHours.js
  59. 20 17
      src/pages/document.ejs
  60. 13 0
      src/services/DataMeter.js
  61. 103 2
      src/services/ProjectAdmin.js
  62. 16 0
      src/services/approval.js
  63. 136 1
      src/services/boom.js
  64. 18 10
      src/services/user.js
  65. 17 1
      src/services/workHours.js
  66. 10 6
      src/utils/exportExcl.js

+ 12 - 11
config/config.js

@@ -29,20 +29,20 @@ const plugins = [
       },
       pwa: pwa
         ? {
-          workboxPluginMode: 'InjectManifest',
-          workboxOptions: {
-            importWorkboxFrom: 'local',
-          },
-        }
+            workboxPluginMode: 'InjectManifest',
+            workboxOptions: {
+              importWorkboxFrom: 'local',
+            },
+          }
         : {},
       ...(!TEST && os.platform() === 'darwin'
         ? {
-          dll: {
-            include: ['dva', 'dva/router', 'dva/saga', 'dva/fetch'],
-            exclude: ['@babel/runtime'],
-          },
-          hardSource: false,
-        }
+            dll: {
+              include: ['dva', 'dva/router', 'dva/saga', 'dva/fetch'],
+              exclude: ['@babel/runtime'],
+            },
+            hardSource: false,
+          }
         : {}),
     },
   ],
@@ -140,6 +140,7 @@ export default {
   chainWebpack: webpackPlugin,
   proxy: {
     '/api': {
+      // target: 'http://192.168.20.152:8888/',
       target: 'http://47.96.12.136:8896/',
       // target: 'http://oraysmart.com:8889/',
       // target: 'http://oraysmart.com:8888/api',

+ 16 - 0
config/router.config.js

@@ -7,6 +7,10 @@ export default [
         path: '/login',
         component: './PurchaseAdmin/PurchaseList/Login/Login',
       },
+      {
+        path: '/mobile/craft',
+        component: './Mobile/DataMeter/Craft',
+      },
       {
         path: '/home',
         component: './PurchaseAdmin/PurchaseList/Index',
@@ -40,6 +44,10 @@ export default [
             path: '/home/report/department',
             component: './PurchaseAdmin/PurchaseList/Report/Department',
           },
+          {
+            path: '/home/report/finance',
+            component: './PurchaseAdmin/PurchaseList/Report/Finance',
+          },
           {
             path: '/home/approval/list',
             component: './PurchaseAdmin/PurchaseList/Approval/List',
@@ -66,6 +74,14 @@ export default [
           },
         ],
       },
+      {
+        path: '/dd-login/:dingUserId',
+        component: './DDLogin/index',
+      },
+      {
+        path: '/mobiletest/chart',
+        component: './Mobile/DataMeter/Chart',
+      },
       {
         component: '404',
       },

+ 4 - 2
package.json

@@ -51,6 +51,7 @@
     "caniuse-lite": "^1.0.30001165",
     "classnames": "^2.2.6",
     "compression-webpack-plugin": "6.0.0",
+    "dingtalk-jsapi": "^2.15.4",
     "dva": "^2.4.1",
     "enquire-js": "^0.2.1",
     "exceljs": "^4.3.0",
@@ -74,6 +75,7 @@
     "omit.js": "^1.0.0",
     "path-to-regexp": "^3.0.0",
     "prop-types": "^15.6.2",
+    "qrcode.react": "^3.1.0",
     "qs": "^6.6.0",
     "range-parser": "1.2.0",
     "rc-animate": "^2.6.0",
@@ -136,10 +138,10 @@
     "tslint": "^5.12.1",
     "tslint-config-prettier": "^1.17.0",
     "tslint-react": "^3.6.0",
+    "typescript": "^4.4.4",
     "umi": "^2.4.4",
     "umi-plugin-ga": "^1.1.3",
-    "umi-plugin-react": "^1.4.2",
-    "typescript": "^4.4.4"
+    "umi-plugin-react": "^1.4.2"
   },
   "optionalDependencies": {
     "puppeteer": "^1.12.1"

+ 36 - 34
public/luckysheet.html

@@ -1,38 +1,40 @@
 <!DOCTYPE html>
 <html lang="en">
-  <head>
-    <meta charset="UTF-8" />
-    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Document</title>
 
-   <!-- 线上发布使用路径 -->
-   <!-- <link rel="stylesheet" href="/bom/Luckysheet/plugins/css/pluginsCss.css" />
-   <link rel="stylesheet" href="/bom/Luckysheet/plugins/plugins.css" />
-   <link rel="stylesheet" href="/bom/Luckysheet/css/luckysheet.css" />
-   <link rel="stylesheet" href="/bom/Luckysheet/assets/iconfont/iconfont.css" />
-   <script src="/bom/Luckysheet/plugins/js/plugin.js"></script>
-   <script src="/bom/Luckysheet/luckysheet.umd.js"></script> -->
+<head>
+  <meta charset="UTF-8" />
+  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <title>Document</title>
 
-   <!-- 本地开发使用路径 -->
-   <link rel='stylesheet' href='http://localhost:3000/plugins/css/pluginsCss.css' />
-   <link rel='stylesheet' href='http://localhost:3000/plugins/plugins.css' />
-   <link rel='stylesheet' href='http://localhost:3000/css/luckysheet.css' />
-   <link rel='stylesheet' href='http://localhost:3000/assets/iconfont/iconfont.css' />
-   <script src="http://localhost:3000/plugins/js/plugin.js"></script>
-   <script src="http://localhost:3000/luckysheet.umd.js"></script>
-  </head>
-  <body>
-    <div
-      id="luckysheet"
-      style="margin:0px;padding:0px;position:absolute;width:100%;height:100%;left: 0px;top: 0px;"
-    ></div>
-  </body>
-  <script>
-    window.render = options => {
-      options = options || {};
-      options.container = 'luckysheet';
-      luckysheet.create(options);
-    };
-  </script>
-</html>
+  <!-- 线上发布使用路径 -->
+  <link rel="stylesheet" href="/bom/Luckysheet/plugins/css/pluginsCss.css" />
+  <link rel="stylesheet" href="/bom/Luckysheet/plugins/plugins.css" />
+  <link rel="stylesheet" href="/bom/Luckysheet/css/luckysheet.css" />
+  <link rel="stylesheet" href="/bom/Luckysheet/assets/iconfont/iconfont.css" />
+  <script src="/bom/Luckysheet/plugins/js/plugin.js"></script>
+  <script src="/bom/Luckysheet/luckysheet.umd.js"></script>
+
+  <!-- 本地开发使用路径 -->
+
+  <link rel='stylesheet' href='http://localhost:3000/plugins/css/pluginsCss.css' />
+  <link rel='stylesheet' href='http://localhost:3000/plugins/plugins.css' />
+  <link rel='stylesheet' href='http://localhost:3000/css/luckysheet.css' />
+  <link rel='stylesheet' href='http://localhost:3000/assets/iconfont/iconfont.css' />
+  <script src="http://localhost:3000/plugins/js/plugin.js"></script>
+  <script src="http://localhost:3000/luckysheet.umd.js"></script>
+</head>
+
+<body>
+  <div id="luckysheet" style="margin:0px;padding:0px;position:absolute;width:100%;height:100%;left: 0px;top: 0px;">
+  </div>
+</body>
+<script>
+  window.render = options => {
+    options = options || {};
+    options.container = 'luckysheet';
+    luckysheet.create(options);
+  };
+</script>
+
+</html>

+ 2 - 1
src/components/Flow/config-toolbar.ts

@@ -231,10 +231,11 @@ namespace NSToolbarConfig {
                   id: item.id,
                   source: item.source,
                   target: item.target,
-                  attrs: item.attrs
+                  attr: JSON.stringify(item.attrs)
                 };
               });
               console.log(data);
+              console.log(JSON.stringify(data));
               localStorage.graphData = JSON.stringify(data);
               return null;
             },

+ 109 - 5
src/components/Flow/index.tsx

@@ -1,5 +1,5 @@
-import { IAppLoad } from '@antv/xflow';
-import React, { useRef, useEffect } from 'react';
+import { IAppLoad, NsGraphCmd } from '@antv/xflow';
+import React, { useRef, useEffect, useImperativeHandle } from 'react';
 /** 交互组件 */
 import {
   /** XFlow核心组件 */
@@ -33,7 +33,7 @@ import { useCmdConfig, initGraphCmds } from './config-cmd';
 /** 配置Menu */
 import { useMenuConfig } from './config-menu';
 /** 配置Toolbar */
-import { useToolbarConfig } from './config-toolbar';
+import { TOOLBAR_ITEMS, useToolbarConfig } from './config-toolbar';
 /** 配置快捷键 */
 import { useKeybindingConfig } from './config-keybinding';
 import { registerNode } from './node/registerNode';
@@ -44,15 +44,17 @@ import CustomFlowchartFormPanel from './node/FlowFormPanel';
 
 import '@antv/xflow/dist/index.css';
 import './index.less';
+import { TYPE } from './node/auditNode/mapServe';
 
 export interface IProps {
   meta: { flowId: string; type: 'edit' };
   flowDetail: any;
   onSelectNode?: Function;
+  parentRef?: any;
 }
 
 export const Demo: React.FC<IProps> = props => {
-  const { meta, flowDetail } = props;
+  const { meta, flowDetail, parentRef } = props;
   const isEdit = meta.type == 'edit';
   const toolbarConfig = useToolbarConfig();
   const menuConfig = useMenuConfig();
@@ -61,6 +63,91 @@ export const Demo: React.FC<IProps> = props => {
   const appRef = useRef<IApplication>();
   const commandConfig: any = useCmdConfig(props);
 
+  // 封装供外部主动调用的接口
+  useImperativeHandle(parentRef, () => ({
+    getGraphData: async cb => {
+      appRef.current.commandService.executeCommand<NsGraphCmd.SaveGraphData.IArgs>(
+        TOOLBAR_ITEMS.SAVE_GRAPH_DATA,
+        {
+          saveGraphDataService: (meta, graphData) => {
+            let data = JSON.parse(JSON.stringify(graphData));
+            let simpleNodes = data?.nodes?.map(curNode => {
+              let childrenNodes = data.edges
+                .map(edge => {
+                  if (edge.source?.cell == curNode.id) {
+                    return data.nodes.find(item => item.id == edge.target?.cell);
+                  }
+                })
+                .filter(item => item);
+              //按优先级排序子节点
+              const children = childrenNodes
+                .sort((a, b) => a.priority - b.priority)
+                .map(item => item.id);
+              const node = {
+                id: curNode.id,
+                type: curNode.type,
+                //条件节点
+                formItems:
+                  curNode.type == TYPE.JUDGE && !curNode.formItems ? '[]' : curNode.formItems,
+                priority: curNode.priority,
+                //审批节点
+                initiator: curNode.type == TYPE.INITIATOR ? curNode.initiator : null,
+                audits: curNode.type == TYPE.AUDIT ? curNode.audits : null,
+                children: children,
+              };
+              return node;
+            });
+            data.nodes = data.nodes.map(item => {
+              let newItem = JSON.parse(JSON.stringify(item));
+              delete newItem.incomingEdges;
+              delete newItem.originData;
+              delete newItem.outgoingEdges;
+              delete newItem.ports.groups;
+              // let ports = { ...item.ports };
+              // delete ports.groups;
+              // return {
+              //   id: item.id,
+              //   renderKey: item.renderKey,
+              //   name: item.name,
+              //   label: item.label,
+              //   width: item.width,
+              //   height: item.height,
+              //   ports: ports,
+              //   isCustom: item.isCustom,
+              //   parentKey: item.parentKey,
+              //   x: item.x,
+              //   y: item.y,
+              //   zIndex: item.zIndex,
+              //   count: item.count,
+              // };
+              return newItem;
+            });
+            // graphData.edges = []
+            data.edges = data.edges.map(item => {
+              // delete item.data;
+              // delete item.attrs;
+              // delete item.sourcePort;
+              // delete item.sourcePortId;
+              // delete item.targetPort;
+              // delete item.targetPortId;
+              // delete item.zIndex;
+              return {
+                id: item.id,
+                source: item.source,
+                target: item.target,
+                // attr: JSON.stringify(item.attrs),
+              };
+            });
+
+            console.log(simpleNodes);
+            console.log(JSON.stringify(data));
+            cb?.(JSON.stringify(data), JSON.stringify(simpleNodes));
+            return data;
+          },
+        }
+      );
+    },
+  }));
   /**
    * @param app 当前XFlow工作空间
    * @param extensionRegistry 当前XFlow配置项
@@ -68,6 +155,7 @@ export const Demo: React.FC<IProps> = props => {
   const onLoad: IAppLoad = async app => {
     appRef.current = app;
     graphRef.current = await app.getGraphInstance();
+    // graphRef.current.disableSnapline()
     renderGraph();
   };
 
@@ -88,11 +176,19 @@ export const Demo: React.FC<IProps> = props => {
   };
   const getConfig = () => {
     const defaultOption = {
+      grid: 1,
       mousewheel: {
         enabled: true,
         /** 将鼠标位置作为中心缩放 */
         zoomAtMousePosition: true,
       },
+      resizing: {
+        enabled: true,
+        minWidth: 0,
+        minHeight: 0,
+        preserveAspectRatio: false,
+      },
+      snapline: false,
     };
     return isEdit
       ? defaultOption
@@ -185,4 +281,12 @@ export const Demo: React.FC<IProps> = props => {
   );
 };
 
-export default Demo;
+// 高阶组件
+const DemoHoc = Demo => {
+  const forwardRef = (props, ref) => {
+    return <Demo parentRef={ref} {...props}></Demo>;
+  };
+  return React.forwardRef(forwardRef);
+};
+
+export default DemoHoc(Demo);

+ 3 - 1
src/components/Flow/node/circle/index.tsx

@@ -33,9 +33,11 @@ export default function CustomRect(props) {
           display: 'flex',
           justifyContent: 'center',
           alignItems: 'center',
+          padding: "0px 8px",
+          lineHeight: 1.2
         }}
       >
-        <span>{label}</span>
+        <span style={{ textAlign: 'center', lineHeight: '20px' }}>{label}</span>
       </div>
     </Badge>
   );

+ 2 - 0
src/components/Flow/node/circle/mapServe.tsx

@@ -19,6 +19,7 @@ export interface IConfig {
   stroke?: string;
   flow_id?: string;
   flow_node_id?: string;
+  process_code?: string;
 }
 
 const Component = (props: any) => {
@@ -79,6 +80,7 @@ const Component = (props: any) => {
             onNodeConfigChange(null, {
               flow_node_id: audit.list.FlowNodes[0]?.id,
               flow_id: value,
+              process_code: audit.list.process_code,
             });
           }}
           options={auditList.map(item => {

+ 15 - 33
src/components/Flow/node/control-map-service/components/edge.tsx

@@ -1,17 +1,7 @@
 import React, { useState, useEffect } from 'react';
 import { FlowchartFormWrapper } from '@antv/xflow';
-import {
-  ColorPicker,
-  InputNumberFiled,
-  InputFiled,
-  SelectField,
-} from '../../fields';
-import {
-  PREFIX,
-  DefaultEdgeConfig,
-  ArrowStrokeMaps,
-  ArrowMaps,
-} from '../../constants';
+import { ColorPicker, InputNumberFiled, InputFiled, SelectField } from '../../fields';
+import { PREFIX, DefaultEdgeConfig, ArrowStrokeMaps, ArrowMaps } from '../../constants';
 
 export type MarkerCfg = {
   width?: number;
@@ -43,7 +33,7 @@ const EdgeComponent = (props: any) => {
     ...DefaultEdgeConfig,
     ...config,
   });
-  console.log(DefaultEdgeConfig, config);
+  console.log(edgeConfig);
 
   useEffect(() => {
     setEdgeConfig({
@@ -75,15 +65,13 @@ const EdgeComponent = (props: any) => {
   const getSrokeDashValue = () => {
     const { attrs = {} } = edgeConfig;
     const { line = {} } = attrs;
-    return line.strokeDasharray && line.strokeDasharray[0] == 5
-      ? 'dash'
-      : 'solid';
+    return line.strokeDasharray && line.strokeDasharray[0] == 5 ? 'dash' : 'solid';
   };
 
   const onEdgeConfigChange = (
     key: string,
     value: number | string | object,
-    type: string = 'line',
+    type: string = 'line'
   ) => {
     /** 全量更新,简化逻辑 */
     if (key === 'arrow') {
@@ -116,7 +104,7 @@ const EdgeComponent = (props: any) => {
         [key]: value,
       },
       type,
-      key === 'arrow' ? 'arrow' : '',
+      key === 'arrow' ? 'arrow' : ''
     );
   };
 
@@ -127,7 +115,7 @@ const EdgeComponent = (props: any) => {
         <InputFiled
           label="标签"
           value={edgeConfig.label}
-          onChange={(value) => {
+          onChange={value => {
             onEdgeConfigChange('label', value);
           }}
         />
@@ -157,12 +145,8 @@ const EdgeComponent = (props: any) => {
               value: 'none',
             },
           ]}
-          onChange={(value) => {
-            onEdgeConfigChange(
-              'arrow',
-              ArrowMaps[value as keyof typeof ArrowMaps],
-              'line',
-            );
+          onChange={value => {
+            onEdgeConfigChange('arrow', ArrowMaps[value as keyof typeof ArrowMaps], 'line');
           }}
         />
 
@@ -181,18 +165,18 @@ const EdgeComponent = (props: any) => {
                 value: 'dash',
               },
             ]}
-            onChange={(value) => {
+            onChange={value => {
               onEdgeConfigChange(
                 'strokeDasharray',
                 ArrowStrokeMaps[value as keyof typeof ArrowStrokeMaps],
-                'line',
+                'line'
               );
             }}
           />
           <InputNumberFiled
             value={getAttrs('strokeWidth')}
             min={1}
-            onChange={(value) => {
+            onChange={value => {
               onEdgeConfigChange('strokeWidth', value, 'line');
             }}
           />
@@ -213,7 +197,7 @@ const EdgeComponent = (props: any) => {
             min={10}
             width={68}
             value={getAttrs('fontSize', 'text') || 12}
-            onChange={(value) => {
+            onChange={value => {
               onEdgeConfigChange('fontSize', value, 'text');
             }}
           />
@@ -228,12 +212,10 @@ const EdgeComponent = (props: any) => {
     </div>
   );
 };
-export const EdgeService: React.FC<any> = (props) => {
+export const EdgeService: React.FC<any> = props => {
   return (
     <FlowchartFormWrapper {...props} type="edge">
-      {(config, plugin) => (
-        <EdgeComponent {...props} plugin={plugin} config={config} />
-      )}
+      {(config, plugin) => <EdgeComponent {...props} plugin={plugin} config={config} />}
     </FlowchartFormWrapper>
   );
 };

+ 1 - 1
src/components/Flow/node/fields/radio.tsx

@@ -29,7 +29,7 @@ const SelectField: React.FC<IProps> = props => {
         }}
       >
         {options.map(item => (
-          <Radio value={item.value}>{item.label}</Radio>
+          <Radio key={`${item.value}-${item.label}`} value={item.value}>{item.label}</Radio>
         ))}
       </Radio.Group>
     </div>

+ 67 - 10
src/components/Flow/node/rect/mapServe.tsx

@@ -1,6 +1,6 @@
 import React, { useState, useEffect } from 'react';
 import { FlowchartFormWrapper } from '@antv/xflow';
-import { Button, message } from 'antd';
+import { Button, message, Select } from 'antd';
 import LuckyExcel from 'luckyexcel';
 import {
   Position,
@@ -13,6 +13,12 @@ import {
 } from '../fields';
 import { PREFIX } from '../constants';
 import { UnityAction } from '@/utils/utils';
+import { connect } from 'dva';
+
+interface ExcelInfo {
+  file_name?: string;
+  excel_cols?: any;
+}
 
 export interface IConfig {
   label?: string;
@@ -30,17 +36,21 @@ export interface IConfig {
   bom_template?: string;
   version_name?: string;
   data?: any;
-  excel_info?: any;
+  excel_info?: ExcelInfo;
+  role_list?: string;
 }
 
 const defaultConfig: IConfig = {
   muti_version: 1,
   is_start_node: 0,
+  excel_info: { file_name: '' },
 };
 
 const Component = (props: any) => {
-  const { config, plugin = {} } = props;
+  const { config, plugin = {}, roleList } = props;
   const { updateNode } = plugin;
+  const [options, setOptions] = useState([]);
+  const [fileName, setFileName] = useState('');
   const [nodeConfig, setNodeConfig] = useState<IConfig>({
     ...defaultConfig,
     ...config,
@@ -79,13 +89,13 @@ const Component = (props: any) => {
       const sheet = exportJson.sheets[0];
       let titleCell = [];
       sheet.celldata.forEach(item => {
-        if(item.r == 0) {
+        if (item.r == 0) {
           // 标题头
-          titleCell.push(item)
+          titleCell.push(item);
         }
         // 生成cid
-        item.v.cid = `${item.r}-${item.c}`
-      })
+        item.v.cid = `${item.r}-${item.c}`;
+      });
       let cell = titleCell.map(item => {
         let value = '';
         if (item.v?.v) {
@@ -111,8 +121,36 @@ const Component = (props: any) => {
       ...config,
     });
   }, [config]);
+
+  const updataFileName = (name: string) => {
+    var idx = name?.lastIndexOf('/');
+    let str = name.substring(idx + 1, name.length);
+    setFileName(str);
+  };
+
+  useEffect(() => {
+    if (config.bom_template) updataFileName(config.bom_template);
+    if (nodeConfig.excel_info?.file_name) updataFileName(nodeConfig.excel_info.file_name);
+  }, [nodeConfig.bom_template, nodeConfig.excel_info.file_name]);
   // console.log(nodeConfig, config)
 
+  useEffect(() => {
+    if (!roleList || roleList.length <= 0) return;
+    let op = [];
+    console.log(roleList);
+    roleList
+      .filter(cur => cur.RoleType == 4)
+      .forEach(item => {
+        op.push({ label: `${item.Name}(${item.ID})`, value: item.ID });
+      });
+    setOptions(op);
+    console.log(op);
+  }, [roleList]);
+
+  // const handleFileNameClick = () => {
+  //   if (nodeConfig.bom_template) window.open(nodeConfig.bom_template);
+  // };
+
   return (
     <div className={`${PREFIX}-panel-body`}>
       <div className={`${PREFIX}-panel-group`}>
@@ -152,7 +190,7 @@ const Component = (props: any) => {
         {nodeConfig.is_start_node == 1 && (
           <>
             <InputFiled
-              label="版本名称"
+              label="模板名称"
               value={nodeConfig.version_name}
               onChange={value => {
                 onNodeConfigChange('version_name', value);
@@ -163,9 +201,26 @@ const Component = (props: any) => {
               onChange={url => onNodeConfigChange('bom_template', url)}
               beforeUpload={beforeUpload}
             />
-            <div>{nodeConfig.excel_info?.file_name}</div>
+            {/* <div onClick={handleFileNameClick}>{fileName}</div> */}
+            <a href={nodeConfig.bom_template}>{fileName}</a>
           </>
         )}
+        <div className="group">
+          <label>权限</label>
+          <Select
+            value={
+              nodeConfig.role_list ? nodeConfig.role_list.split(',').map(item => Number(item)) : []
+            }
+            mode="multiple"
+            allowClear
+            style={{ width: '100%' }}
+            placeholder="选择权限"
+            onChange={(v: number[]) => {
+              onNodeConfigChange('role_list', v.join(','));
+            }}
+            options={options}
+          />
+        </div>
       </div>
       <div className={`${PREFIX}-panel-group`}>
         <h5>样式</h5>
@@ -229,10 +284,12 @@ const Component = (props: any) => {
   );
 };
 
-export default function RecthServe(props: any) {
+function RecthServe(props: any) {
   return (
     <FlowchartFormWrapper {...props}>
       {(config, plugin) => <Component {...props} plugin={plugin} config={config} />}
     </FlowchartFormWrapper>
   );
 }
+
+export default connect(({ user }) => ({ roleList: user.roleList }))(RecthServe);

+ 21 - 3
src/components/Flow/node/registerNode.tsx

@@ -1,10 +1,11 @@
-import React from "react"
+import React from 'react';
 import Rect, { RectServe } from './rect';
 import Circle, { CircleServe } from './circle';
+import AuditNode, { AuditServe } from './auditNode';
 
+import judgeNode, { judgeServe } from './judgeNode';
 
 export const registerNode = [
-
   {
     component: Rect,
     popover: () => <div>业务组件</div>,
@@ -23,5 +24,22 @@ export const registerNode = [
     label: '审批节点',
     service: CircleServe,
   },
-
+  {
+    component: AuditNode,
+    popover: () => <div>动作节点</div>,
+    name: 'custom-audit',
+    width: 130,
+    height: 50,
+    label: '动作节点',
+    service: AuditServe,
+  },
+  {
+    component: judgeNode,
+    popover: () => <div>条件节点</div>,
+    name: 'custom-judge',
+    width: 130,
+    height: 50,
+    label: '条件节点',
+    service: judgeServe,
+  },
 ];

+ 43 - 43
src/global.less

@@ -42,20 +42,20 @@ ol {
 }
 
 @media (max-width: @screen-xs) {
-  .ant-table {
-    width: 100%;
-    overflow-x: auto;
-    &-thead > tr,
-    &-tbody > tr {
-      > th,
-      > td {
-        white-space: pre;
-        > span {
-          display: block;
-        }
-      }
-    }
-  }
+  // .ant-table {
+  //   width: 100%;
+  //   overflow-x: auto;
+  //   &-thead > tr,
+  //   &-tbody > tr {
+  //     > th,
+  //     > td {
+  //       white-space: pre;
+  //       > span {
+  //         display: block;
+  //       }
+  //     }
+  //   }
+  // }
 }
 
 //.ant-table-wrapper {
@@ -109,35 +109,35 @@ ol {
   }
 }
 
-.ant-table {
-  table {
-    border: 1px solid #e8e8e8;
-    border-right: 0;
-    border-bottom: 0;
-
-    .ant-table-bordered .ant-table-thead > tr > th, .ant-table-bordered .ant-table-tbody > tr > td {
-      border-right: 1px solid #e8e8e8;
-    }
-    .ant-table-thead > tr > th {
-      color: rgba(0, 0, 0, 0.85);
-      font-weight: 500;
-      text-align: left;
-      background: #fafafa;
-      border-bottom: 1px solid #e8e8e8;
-      transition: background 0.3s ease;
-      border-right: 1px solid #e8e8e8;
-    }
-    .ant-table-tbody > tr > td {
-      border-bottom: 1px solid #e8e8e8;
-      border-right: 1px solid #e8e8e8;
-      background: white;
-    }
-
-    .ant-table-thead > tr:first-child > th:first-child {
-      border-top-left-radius: 4px;
-    }
-  }
-}
+// .ant-table {
+//   table {
+//     border: 1px solid #e8e8e8;
+//     border-right: 0;
+//     border-bottom: 0;
+
+//     .ant-table-bordered .ant-table-thead > tr > th, .ant-table-bordered .ant-table-tbody > tr > td {
+//       border-right: 1px solid #e8e8e8;
+//     }
+//     .ant-table-thead > tr > th {
+//       color: rgba(0, 0, 0, 0.85);
+//       font-weight: 500;
+//       text-align: left;
+//       background: #fafafa;
+//       border-bottom: 1px solid #e8e8e8;
+//       transition: background 0.3s ease;
+//       border-right: 1px solid #e8e8e8;
+//     }
+//     .ant-table-tbody > tr > td {
+//       border-bottom: 1px solid #e8e8e8;
+//       border-right: 1px solid #e8e8e8;
+//       background: white;
+//     }
+
+//     .ant-table-thead > tr:first-child > th:first-child {
+//       border-top-left-radius: 4px;
+//     }
+//   }
+// }
 
 /*滚动条整体样式*/
 ::-webkit-scrollbar {

+ 80 - 5
src/models/user.js

@@ -2,14 +2,56 @@ import {
   query as queryUsers,
   queryCurrent,
   queryCurrentV2,
+  queryUserRole,
   queryUnreadNotification,
   SetNotificationRead,
 } from '@/services/user';
+import { queryDepV2 } from '@/services/approval';
 import { ShowUnreadNotification } from '@/utils/utils';
 import { queryDeviceItemRealtimeData } from '@/services/DeviceAdmin';
 import { queryUserList } from '@/services/plant';
-import { queryProjectMenu, queryUserDetail } from '@/services/SysAdmin';
+import { queryProjectMenu, queryUserDetail, queryRole } from '@/services/SysAdmin';
 
+function getDepUserTree(data) {
+  data.title = `${data.Name}`;
+  data.id = data.ID;
+  data.value = data.ID;
+  // data.selectable = false;
+  if (!data.children) data.children = new Array();
+
+  if (data.children) {
+    data.children.forEach(item => {
+      getDepUserTree(item);
+    });
+  }
+
+  if (data.Users && data.Users.length !== 0) {
+    data.Users.forEach(item => {
+      item.title = item.CName;
+      item.id = item.ID + '||' + data.ID;
+      item.value = item.ID + '||' + data.ID;
+      // item.selectable = true;
+      item.DepId = data.ID;
+      data.children.push(item);
+    });
+  }
+  return data;
+}
+
+const getRoleList = data => {
+  let roleList = [];
+  (data || []).forEach(dep => {
+    (dep.Role || []).forEach(role => {
+      roleList.push(role);
+    });
+
+    if (dep.children) {
+      let res = getRoleList(dep.children, roleList);
+      roleList = res.concat(res);
+    }
+  });
+  return roleList;
+};
 export default {
   namespace: 'user',
 
@@ -19,6 +61,8 @@ export default {
     message: {},
     userList: [],
     depRole: [],
+    depUserTree: [],
+    roleList: [],
   },
 
   effects: {
@@ -48,25 +92,29 @@ export default {
         user.Permissions?.forEach(item => {
           permission = {
             ...permission,
-            ...item.Menus
-          }
-        })
+            ...item.Menus,
+          };
+        });
         try {
           localStorage.setItem('depId', user.DepId);
           if (payload?.ID) {
             const { data: resData } = yield call(queryProjectMenu, payload);
             // permission = resData[0] ? resData[0]?.Menus : {};
-            
           }
         } catch (error) {
           console.error(error);
         }
+        const resRole = yield call(queryUserRole, user.ID);
+        let roleList = getRoleList(resRole.data.Dep);
+        console.log(roleList);
+
         yield put({
           type: 'saveCurrentUser',
           payload: {
             ...user,
             // Permission: {},
             Permission: permission,
+            roleList: roleList,
           },
         });
       }
@@ -107,6 +155,27 @@ export default {
         });
       }
     },
+    *getRoleList({ payload }, { call, put }) {
+      const response = yield call(queryRole, payload);
+      if (response) {
+        yield put({
+          type: 'saveState',
+          payload: { roleList: response.data.list },
+        });
+      }
+    },
+    *fetchDepV2({ payload, callback }, { call, put }) {
+      const response = yield call(queryDepV2, { pageSize: 999999 });
+      if (response) {
+        const depUserTree = response.data.list.map(item => {
+          return getDepUserTree(item);
+        });
+        yield put({
+          type: 'saveState',
+          payload: { depUserTree },
+        });
+      }
+    },
   },
 
   reducers: {
@@ -152,6 +221,12 @@ export default {
         userList: payload,
       };
     },
+    saveState(state, action) {
+      return {
+        ...state,
+        ...action.payload,
+      };
+    },
   },
 
   subscriptions: {

+ 1 - 0
src/models/xflow.js

@@ -7,6 +7,7 @@ export default {
     OSSData: {},
     flowDetail: { nodes: [], edges: [] },
     auditList: [],
+    formData: [],
   },
 
   effects: {

+ 116 - 120
src/pages/PurchaseAdmin/PurchaseList/Approval/ApprovalModal.js

@@ -1,11 +1,8 @@
 import React, { useState, useEffect } from 'react';
-import { Form } from '@ant-design/compatible';
-import '@ant-design/compatible/assets/index.css';
-import { Select, Modal, Input, TreeSelect } from 'antd';
+import { Form, Select, Modal, Input, TreeSelect } from 'antd';
 import moment from 'moment';
 import provinces from './provinces';
 import { queryApproval } from '@/services/approval';
-// import project from '@/pages/ProjectAdmin/ProjectAdmin/models/project';
 const { Option } = Select;
 const { TreeNode } = TreeSelect;
 // 新建
@@ -16,14 +13,16 @@ function AddModal(props) {
     visible,
     onClose,
     onOk,
-    form,
     data,
+    currentUser,
+    depUserTree,
     flowList = [],
     industryList = [],
     typeList = [],
     disabled,
     loading,
   } = props;
+  const [form] = Form.useForm();
   const [codes, setCodes] = useState({
     type: '',
     industry: '',
@@ -34,51 +33,28 @@ function AddModal(props) {
   const [type, setType] = useState({});
 
   const handleOk = () => {
-    form.validateFields((err, fieldsValue) => {
-      if (err) return;
+    form.validateFields().then(fieldsValue => {
       let values = { ...fieldsValue, id: data.id };
-      //项目分类为研发时
-      if (fieldsValue.type_id == 7) {
-        values.project_name = fieldsValue.project_name;
-        values.flow_id = Number(fieldsValue.flow_id);
-        values.type_id = Number(fieldsValue.type_id);
-        // 获得flow下第一个node的id
-        values.node_id = flowList.find(item => item.id == values.flow_id).Nodes[0].id;
-        //以下为测试用默认值
-        // values.industry_id = 0;
-        // values.location = '';
-        // values.location_code = '';
-        // values.project_full_code = '';
-        // values.name = '';
-        // values.version = '';
-
-        //通过当前项目数生成项目编号
-        // dispatch({
-        //   type: 'approval/queryApproval',
-        //   callback: data => {
-        //     let project_full_code = '';
-        //     if (total < 10) {
-        //       project_full_code = '00' + data.pagination.total;
-        //     } else if (total < 100) project_full_code = '0' + data.pagination.total;
-        //     else project_full_code = data.pagination.total;
-        //     values.project_full_code = `RD${moment().format('YYYYMM')}${project_full_code}`;
-        //     onOk(values);
-        //   },
-        // });
-        onOk(values);
-      } else {
-        values.project_name = fieldsValue.project_name;
-        values.type_id = Number(fieldsValue.type_id);
+      values.project_name = fieldsValue.project_name;
+      values.flow_id = Number(fieldsValue.flow_id);
+      values.type_id = Number(fieldsValue.type_id);
+      // 获得flow下第一个node的id
+      values.node_id = flowList.find(item => item.id == values.flow_id).Nodes[0].id;
+      //项目分类为不为研发时
+      if (fieldsValue.type_id != 7) {
         values.industry_id = Number(fieldsValue.industry_id);
-        values.flow_id = Number(fieldsValue.flow_id);
-        // 获得flow下第一个node的id
-        values.node_id = flowList.find(item => item.id == values.flow_id).Nodes[0].id;
         let [location, location_code] = fieldsValue.location.split('##');
         values.location = location;
         values.location_code = location_code;
         values.project_full_code = `${codes.type}${codes.industry}${codes.location}${codes.name}${codes.version}`;
-        onOk(values);
       }
+      if (fieldsValue.author) {
+        values.author = Number(fieldsValue.author.split('||')[0]);
+        values.author_dep_id = Number(fieldsValue.author.split('||')[1]);
+      } else {
+        values.author = null;
+      }
+      onOk(values);
     });
   };
 
@@ -88,7 +64,6 @@ function AddModal(props) {
       let code = item.code || '';
       if (code.length == 4) {
         code = code.substr(1);
-        console.log(code);
       }
       if (code) {
         title += `(${code})`;
@@ -108,7 +83,7 @@ function AddModal(props) {
       type: item.code,
     });
     setType(item);
-    form.setFieldsValue({ flow_id: id == 7 ? '4' : '1'});
+    form.setFieldsValue({ flow_id: id == 7 ? '4' : '1' });
   };
   const changeIndustry = id => {
     const item = industryList.find(item => item.id == id);
@@ -133,7 +108,7 @@ function AddModal(props) {
   const onBlurName = e => {
     let value = e.target.value.toUpperCase();
     while (value.length < 3) {
-      value = value + "V";
+      value = value + 'V';
     }
     form.setFieldsValue({
       name: value,
@@ -145,12 +120,14 @@ function AddModal(props) {
   };
 
   const renderDetail = () => {
-    return <>
-      <Form.Item label="行业名称">
-        {form.getFieldDecorator('industry_id', {
-          initialValue: String(data.industry_id || ''),
-          rules: [{ required: true, message: '请选择行业名称' }],
-        })(
+    return (
+      <>
+        <Form.Item
+          label="行业名称"
+          name="industry_id"
+          initialValue={String(data.industry_id || '')}
+          rules={[{ required: true, message: '请选择行业名称' }]}
+        >
           <Select style={{ width: '100%' }} onChange={changeIndustry}>
             {industryList.map(item => (
               <Option key={item.id}>
@@ -158,55 +135,54 @@ function AddModal(props) {
               </Option>
             ))}
           </Select>
-        )}
-      </Form.Item>
-      <Form.Item label="项目地区">
-        {form.getFieldDecorator('location', {
-          initialValue: data.location,
-          rules: [{ required: true, message: '请选择项目地区' }],
-        })(
+        </Form.Item>
+        <Form.Item
+          label="项目地区"
+          name="location"
+          initialValue={data.location}
+          rules={[{ required: true, message: '请选择项目地区' }]}
+        >
           <TreeSelect
             dropdownStyle={{ maxHeight: 300, overflow: 'auto' }}
             onChange={changeLocation}
           >
             {renderTreeNodes(provinces)}
           </TreeSelect>
-        )}
-      </Form.Item>
-      <Form.Item label="项目简称">
-        {form.getFieldDecorator('name', {
-          initialValue: data.name,
-          rules: [
+        </Form.Item>
+        <Form.Item
+          label="项目简称"
+          name="name"
+          initialValue={data.name}
+          rules={[
             { required: true, message: '请输入项目简称' },
             {
               validator: (rule, value, callback) => {
-                if (value.match(/[^A-Za-z]/g)) {
-                  callback('项目简称只能是英文字符');
-                } else {
-                  callback();
-                }
+                if (value.match(/[^A-Za-z]/g)) callback('项目简称只能是英文字符');
+                else callback();
               },
             },
-          ],
-        })(<Input maxLength={3} onBlur={onBlurName} />)}
-      </Form.Item>
-      <Form.Item label="项目期数">
-        {form.getFieldDecorator('version', {
-          initialValue: data.version,
-          rules: [{ required: true, message: '请选择项目期数' }],
-        })(
+          ]}
+        >
+          <Input maxLength={3} onBlur={onBlurName} />
+        </Form.Item>
+        <Form.Item
+          label="项目期数"
+          name="version"
+          initialValue={data.version}
+          rules={[{ required: true, message: '请选择项目期数' }]}
+        >
           <Select style={{ width: '100%' }} onChange={changeVersion}>
             {['一期', '二期', '三期', '四期', '五期'].map((item, index) => (
               <Option key={index + 1}>{item}</Option>
             ))}
           </Select>
-        )}
-      </Form.Item>
-      <Form.Item label="项目编号">
-        {codes.type || '***'}-{codes.industry || '***'}-{codes.location || '***'}-
-        {codes.name || '***'}-{codes.version || '*'}
-      </Form.Item>
-    </>;
+        </Form.Item>
+        <Form.Item label="项目编号">
+          {codes.type || '***'}-{codes.industry || '***'}-{codes.location || '***'}-
+          {codes.name || '***'}-{codes.version || '*'}
+        </Form.Item>
+      </>
+    );
   };
 
   // const renderCode = () => {
@@ -253,46 +229,66 @@ function AddModal(props) {
       onCancel={onClose}
       onOk={handleOk}
     >
-      <Form labelCol={{ span: 5 }} wrapperCol={{ span: 15 }}>
-        <Form.Item label="项目名称">
-          {form.getFieldDecorator('project_name', {
-            initialValue: String(data.project_name || ''),
-            rules: [{ required: true, message: '请输入项目名称' }],
-          })(
-            <Input style={{ width: '100%' }}/>
-          )}
+      <Form labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} form={form}>
+        <Form.Item
+          label="项目名称"
+          name="project_name"
+          initialValue={String(data.project_name || '')}
+          rules={[{ required: true, message: '请输入项目名称' }]}
+        >
+          <Input style={{ width: '100%' }} />
         </Form.Item>
-        <Form.Item label="项目类别">
-          {form.getFieldDecorator('type_id', {
-            initialValue: String(data.type_id || ''),
-            rules: [{ required: true, message: '请选择项目类别' }],
-          })(
-            <Select style={{ width: '100%' }} onChange={changeType}>
-              {typeList.map(item => (
-                <Option key={item.id}>
-                  {item.name}({item.code})
-                </Option>
-              ))}
-            </Select>
-          )}
+        <Form.Item
+          label="项目类别"
+          name="type_id"
+          initialValue={String(data.type_id || '')}
+          rules={[{ required: true, message: '请选择项目类别' }]}
+        >
+          <Select style={{ width: '100%' }} onChange={changeType}>
+            {typeList.map(item => (
+              <Option key={item.id}>
+                {item.name}({item.code})
+              </Option>
+            ))}
+          </Select>
         </Form.Item>
-        <Form.Item label="流程">
-          {form.getFieldDecorator('flow_id', {
-            initialValue: String(data.flow_id || ''),
-            rules: [{ required: true, message: '请选择流程' }],
-          })(
-            <Select style={{ width: '100%' }} disabled>
-              {flowList
-                .filter(item => item && (item.id != 2 && item.id != 3))
-                .map(item => (
-                  <Option key={item.id}>{item.name}</Option>
-                ))}
-            </Select>
-          )}
+        <Form.Item
+          label="流程"
+          name="flow_id"
+          initialValue={String(data.flow_id || '')}
+          rules={[{ required: true, message: '请选择流程' }]}
+        >
+          <Select style={{ width: '100%' }} disabled>
+            {flowList
+              .filter(item => item && item.id != 2 && item.id != 3)
+              .map(item => (
+                <Option key={item.id}>{item.name}</Option>
+              ))}
+          </Select>
         </Form.Item>
+        {currentUser.IsSuper && (
+          <Form.Item
+            label="售前经理"
+            name="author"
+            initialValue={String(
+              data.author && data.author_dep_id ? `${data.author}||${data.author_dep_id}` : ''
+            )}
+          >
+            <TreeSelect
+              showSearch
+              allowClear
+              style={{ width: '100%' }}
+              multiple={false}
+              filterTreeNode={(input, option) => {
+                return option.props.title === input;
+              }}
+              treeData={depUserTree}
+            />
+          </Form.Item>
+        )}
         {type?.id != 7 && renderDetail()}
       </Form>
     </Modal>
   );
 }
-export default Form.create()(AddModal);
+export default AddModal;

+ 77 - 71
src/pages/PurchaseAdmin/PurchaseList/Approval/Auth.js

@@ -1,7 +1,5 @@
 import React, { useState, useEffect, useMemo } from 'react';
-import { Form } from '@ant-design/compatible';
-import '@ant-design/compatible/assets/index.css';
-import { Table, Button, Divider, Modal, Popover, Input, Select } from 'antd';
+import { Table, Button, Form, Divider, Modal, Popover, Input, Select } from 'antd';
 import moment from 'moment';
 import router from 'umi/router';
 import styles from './List.less';
@@ -21,20 +19,19 @@ const STATUS = [
     value: 1,
     label: '转执行',
   },
+  {
+    value: 2,
+    label: '转运营',
+  },
+  {
+    value: 3,
+    label: '转质保',
+  },
 ];
 
 function Auth(props) {
-  const {
-    industryList,
-    typeList,
-    data,
-    flowList,
-    currentUser,
-    depRole,
-    dispatch,
-    loading,
-    form,
-  } = props;
+  const { industryList, typeList, data, flowList, currentUser, depRole, dispatch, loading } = props;
+  const [form] = Form.useForm();
   const [visible, setVisible] = useState(false);
   const [detailVisible, setDetailVisible] = useState(false);
   const [rejectVisible, setRejectVisible] = useState(false);
@@ -51,7 +48,7 @@ function Auth(props) {
     {
       title: '分类',
       dataIndex: 'TypeInfo',
-      render: TypeInfo => TypeInfo ?`${TypeInfo.name}(${TypeInfo.code})`: "-",
+      render: TypeInfo => (TypeInfo ? `${TypeInfo.name}(${TypeInfo.code})` : '-'),
     },
     /*
     {
@@ -76,22 +73,24 @@ function Auth(props) {
     */
     {
       title: '流程',
-      dataIndex: 'FlowInfo.name',
+      dataIndex: ['FlowInfo', 'name'],
     },
     {
       title: '状态',
       dataIndex: 'project_status',
       render: project_status => {
-        return project_status === 0 ? <>售前</> : <>转执行</>;
+        // return project_status === 0 ? <>售前</> : <>转执行</>;
         //若添加其他状态则启用以下switch case:
-        /*
-        switch (project_status){
+        switch (project_status) {
           case 0:
-            return <>{'售前'}</>;
+            return <>售前</>;
           case 1:
-            return <>{'转执行'}</>;
+            return <>转执行</>;
+          case 2:
+            return <>转运营</>;
+          case 3:
+            return <>转质保</>;
         }
-        */
       },
     },
     {
@@ -125,31 +124,33 @@ function Auth(props) {
       },
     },
     {
-      title: '创建人',
-      dataIndex: 'AuthorUser.CName',
+      title: '售前项目经理',
+      dataIndex: 'AuthorUser',
+      render: AuthorUser => (AuthorUser ? AuthorUser.CName : '-'),
     },
     {
       title: '创建时间',
       dataIndex: 'c_time',
       render: c_time => moment(c_time).format('YYYY.MM.DD'),
     },
+    {
+      title: '执行经理',
+      dataIndex: 'Leader',
+      render: Leader => (Leader ? Leader.CName : '-'),
+    },
     {
       title: '操作',
       render: record => (
         <>
-          {record.type_id != 7 && (
-            <>
-              <a
-                onClick={() => {
-                  setCurrentItem(record);
-                  setDetailVisible(true);
-                }}
-              >
-                项目详情
-              </a>
-              <Divider type="vertical" />
-            </>
-          )}
+          <a
+            onClick={() => {
+              setCurrentItem(record);
+              setDetailVisible(true);
+            }}
+          >
+            项目详情
+          </a>
+          <Divider type="vertical" />
           <a
             onClick={() => {
               setCurrentItem(record);
@@ -164,11 +165,15 @@ function Auth(props) {
   ];
 
   const canAuth = useMemo(() => {
-    let { NodeInfo, audit_status } = currentItem;
+    let { NodeInfo, audit_status, project_status } = currentItem;
     if (!NodeInfo || flowList.length == 0 || depRole.length == 0) return;
     if (audit_status != 1) return;
     let flow = flowList.find(item => item.id == NodeInfo.flow_id);
     if (!flow) return false;
+
+    if (project_status == 2) return currentItem.opt_manager_id == currentUser.ID;
+    if (project_status == 3) return currentItem.wty_manager_id == currentUser.ID;
+
     let { NodeAudits } = flow.Nodes.find(item => item.id == NodeInfo.id);
 
     const role = depRole.find(item => {
@@ -229,45 +234,46 @@ function Auth(props) {
   };
 
   const handleSearch = () => {
-    form.validateFields((error, { projectCode, projectStatus }) => {
-      // console.log(error,values);
-      let params = {};
-      params.project_code = projectCode;
-      params.project_status = projectStatus;
+    const { projectName, projectCode, projectStatus } = form.getFieldsValue();
+    // console.log(error,values);
+    let params = {};
+    params.project_name = projectName;
+    params.project_code = projectCode?.toUpperCase();
+    params.project_status = projectStatus;
 
-      dispatch({
-        type: 'approval/queryAuth',
-        payload: params,
-      });
+    dispatch({
+      type: 'approval/queryAuth',
+      payload: params,
     });
   };
 
   const renderSearch = () => {
     return (
-      <Form style={{ marginBottom: 20 }} layout="inline">
-        <Form.Item label="项目编号">
-          {form.getFieldDecorator('projectCode', {
-            initialValue: null,
-          })(<Input style={{ width: 200 }}></Input>)}
+      <Form
+        style={{ marginBottom: 20 }}
+        layout="inline"
+        initialValues={{ projectName: null, projectCode: null, projectStatus: null }}
+        form={form}
+      >
+        <Form.Item label="项目名称" name="projectName">
+          <Input style={{ width: 200 }} />
         </Form.Item>
-        <Form.Item label="状态">
-          {form.getFieldDecorator('projectStatus', {
-            initialValue: null,
-          })(
-            <Select
-              allowClear
-              showSearch
-              style={{ width: 120 }}
-              filterOption={(input, option) =>
-                option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
-              }
-            >
-              <Option value={null}>全部</Option>
-              {STATUS.map(item => (
-                <Option key={item.value}>{item.label}</Option>
-              ))}
-            </Select>
-          )}
+        <Form.Item label="项目编号" name="projectCode">
+          <Input style={{ width: 200 }} />
+        </Form.Item>
+        <Form.Item label="状态" name="projectStatus">
+          <Select
+            showSearch
+            style={{ width: 120 }}
+            filterOption={(input, option) =>
+              option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }
+          >
+            <Option value={null}>全部</Option>
+            {STATUS.map(item => (
+              <Option key={item.value}>{item.label}</Option>
+            ))}
+          </Select>
         </Form.Item>
         <Form.Item>
           <Button type="primary" loading={loading} onClick={handleSearch}>
@@ -337,4 +343,4 @@ export default connect(({ approval, user, loading }) => ({
   currentUser: user.currentUser,
   depRole: user.depRole,
   loading: loading.models.approval,
-}))(Form.create()(Auth));
+}))(Auth);

+ 18 - 9
src/pages/PurchaseAdmin/PurchaseList/Approval/AuthModal.js

@@ -1,25 +1,34 @@
 import React, { useState, useEffect, useMemo } from 'react';
-import { Form } from '@ant-design/compatible';
-import '@ant-design/compatible/assets/index.css';
-import { Button, Modal, Steps } from 'antd';
+import { Button, Form, Modal, Steps } from 'antd';
 
 const { Step } = Steps;
 // 新建
 function AuthModal(props) {
   const { visible, onClose, onAuth, form, data, flowList = [], canAuth, loading } = props;
 
-  const folw = useMemo(() => {
+  const flow = useMemo(() => {
     if (!data.flow_id) return {};
     return flowList.find(item => item.id == data.flow_id) || {};
   }, [flowList, data]);
 
   const current = useMemo(() => {
     if (!data.node_id) return 0;
-    return folw.Nodes?.findIndex(item => item.id == data.node_id);
+    return flow.Nodes?.findIndex(item => item.id == data.node_id);
   }, [flowList, data]);
 
   const getAudits = nodeInfo => {
-    return (nodeInfo || []).map(item => item.AuthorRoleInfo.Name).join(',');
+    switch (nodeInfo.id) {
+      case 11:
+        return '执行项目经理';
+      case 12:
+        return '运营经理';
+      case 13:
+        return '执行项目经理';
+      case 14:
+        return '质保经理';
+      default:
+        return (nodeInfo.NodeAudits || []).map(item => item.AuthorRoleInfo.Name).join(',');
+    }
   };
 
   const renderFooter = () => {
@@ -42,7 +51,7 @@ function AuthModal(props) {
 
   return (
     <Modal
-      title="项目立项"
+      title="审核详情"
       width={800}
       visible={visible}
       onCancel={onClose}
@@ -50,11 +59,11 @@ function AuthModal(props) {
     >
       <Steps current={current}>
         {/* <Steps current={data?.node_id}> */}
-        {(folw.Nodes || []).map(item => (
+        {(flow.Nodes || []).map(item => (
           <Step
             key={item.id}
             title={item.node}
-            description={`审批人:${getAudits(item.NodeAudits)}`}
+            description={`审批人:${getAudits(item)}`}
           />
         ))}
       </Steps>

+ 102 - 24
src/pages/PurchaseAdmin/PurchaseList/Approval/DetailModal.js

@@ -1,7 +1,8 @@
-import React, { useState, useEffect } from 'react';
-import { Form } from '@ant-design/compatible';
-import '@ant-design/compatible/assets/index.css';
-import { Modal } from 'antd';
+import React, { useState, useEffect, useMemo } from 'react';
+import { Form, Modal, Steps } from 'antd';
+import styles from './DetailModal.less';
+
+const { Step } = Steps;
 // 新建
 function DetailModal(props) {
   const { visible, onClose, onOk, form, data, flowList = [], disabled, loading } = props;
@@ -24,31 +25,108 @@ function DetailModal(props) {
     });
   }, [data, visible]);
 
-  return (
-    <Modal title="项目立项" visible={visible} onCancel={onClose} footer={null}>
-      <Form labelCol={{ span: 5 }} wrapperCol={{ span: 15 }}>
-        <Form.Item label="项目名称">{data.project_name}</Form.Item>
-        {data.TypeInfo && <Form.Item label="项目类别">{data.TypeInfo?.name}</Form.Item>}
-        {data.IndustryInfo && <Form.Item label="行业名称">{data.IndustryInfo?.name}</Form.Item>}
-        <Form.Item label="流程">{flowList.find(item => item.id == data.flow_id)?.name}</Form.Item>
-        {data.location && (
-          <Form.Item label="项目地区">
-            {data.location}({data.location_code})
+  const renderDetail = () => (
+    <>
+      <div className={styles.subTitle}>项目详情</div>
+      <Form labelCol={{ span: 4 }} wrapperCol={{ span: 18 }}>
+        <Form.Item className={styles.formItem} label="项目名称">
+          {data.project_name}
+        </Form.Item>
+        {data.TypeInfo && (
+          <Form.Item className={styles.formItem} label="项目类别">
+            {data.TypeInfo?.name}
           </Form.Item>
         )}
-        {data.name && <Form.Item label="项目简称">{data.name}</Form.Item>}
-
-        {data.version && <Form.Item label="项目批次">{data.version}期</Form.Item>}
-
-        <Form.Item label="审核人">{data.AuthorUser?.CName}</Form.Item>
-        <Form.Item label="所属部门">{data.AuthorDepInfo?.Name}</Form.Item>
-        {codes.type && (
-          <Form.Item label="项目编号">
-            {codes.type || '***'}-{codes.industry || '***'}-{codes.location || '***'}-
-            {codes.name || '***'}-{codes.version || '*'}
+        <Form.Item className={styles.formItem} label="流程">
+          {flowList.find(item => item.id == data.flow_id)?.name}
+        </Form.Item>
+        {data.type_id != 7 && (
+          <>
+            <Form.Item className={styles.formItem} label="行业名称">
+              {data.IndustryInfo?.name}
+            </Form.Item>
+            <Form.Item className={styles.formItem} label="项目地区">
+              {data.location}({data.location_code})
+            </Form.Item>
+            <Form.Item className={styles.formItem} label="项目简称">
+              {data.name}
+            </Form.Item>
+            <Form.Item className={styles.formItem} label="项目批次">
+              {data.version}期
+            </Form.Item>
+          </>
+        )}
+        {data.AuthorUser && (
+          <Form.Item className={styles.formItem} label="售前项目经理">
+            {data.AuthorUser.CName}
+          </Form.Item>
+        )}
+        {data.AuthorDepInfo && (
+          <Form.Item className={styles.formItem} label="所属部门">
+            {data.AuthorDepInfo.Name}
+          </Form.Item>
+        )}
+        {data.project_full_code && (
+          <Form.Item className={styles.formItem} label="项目编号">
+            {data.project_full_code}
+          </Form.Item>
+        )}
+        {data.WtyManager && (
+          <Form.Item className={styles.formItem} label="质保经理">
+            {data.WtyManager.CName}
+          </Form.Item>
+        )}
+        {data.OptManager && (
+          <Form.Item className={styles.formItem} label="运营经理">
+            {data.OptManager.CName}
           </Form.Item>
         )}
       </Form>
+    </>
+  );
+
+  const flow = useMemo(() => {
+    if (!data.flow_id) return {};
+    return flowList.find(item => item.id == data.flow_id) || {};
+  }, [flowList, data]);
+
+  const current = useMemo(() => {
+    if (!data.node_id) return 0;
+    return flow.Nodes?.findIndex(item => item.id == data.node_id);
+  }, [flowList, data]);
+
+  const getAudits = nodeInfo => {
+    switch (nodeInfo.id) {
+      case 11:
+        return '执行项目经理';
+      case 12:
+        return '运营经理';
+      case 13:
+        return '执行项目经理';
+      case 14:
+        return '质保经理';
+      default:
+        return (nodeInfo.NodeAudits || []).map(item => item.AuthorRoleInfo.Name).join(',');
+    }
+  };
+
+  const renderAuth = () => (
+    <div className={styles.authDetail}>
+      <div className={styles.subTitle}>审核详情</div>
+      <Steps className={styles.auth} current={current}>
+        {/* <Steps current={data?.node_id}> */}
+        {(flow.Nodes || []).map(item => (
+          <Step key={item.id} title={item.node} description={`审批人:${getAudits(item)}`} />
+        ))}
+      </Steps>
+    </div>
+  );
+
+  return (
+    <Modal title="项目详情" width={800} visible={visible} onCancel={onClose} footer={null}>
+      {/* {data.type_id != 7 && renderDetail()} */}
+      {renderDetail()}
+      {data.audit_status != 0 && renderAuth()}
     </Modal>
   );
 }

+ 35 - 74
src/pages/PurchaseAdmin/PurchaseList/Approval/ExecutionModal.js

@@ -1,18 +1,16 @@
 import React, { useState, useEffect } from 'react';
-import { Form } from '@ant-design/compatible';
-import '@ant-design/compatible/assets/index.css';
-import { Select, Modal, Input, TreeSelect } from 'antd';
-import { connect } from 'dva'
+import { Form, Select, Modal, Input, TreeSelect } from 'antd';
+import { connect } from 'dva';
 const { Option } = Select;
 const { TreeNode } = TreeSelect;
 
 function ExecutionModal(props) {
-  const { visible, onOk, onClose, form, currentItem, flowList = [], loading, depUserTree, dispatch } = props;
+  const { visible, onOk, onClose, currentItem, loading, depUserTree, dispatch } = props;
+  const [form] = Form.useForm();
 
   const handleOk = () => {
-    form.validateFields((err, {managerID, contractStatus}) => {
-      if (err) return;
-      const [exe_manager_id,dep_id] = managerID.split("||")
+    form.validateFields().then(({ managerID, contractStatus }) => {
+      const [exe_manager_id, dep_id] = managerID.split('||');
       dispatch({
         type: 'approval/startExecution',
         payload: {
@@ -22,40 +20,10 @@ function ExecutionModal(props) {
           exe_manager_id: Number(exe_manager_id),
         },
         callback: () => onOk(),
-      })
+      });
     });
   };
-
-  const onChangeManager = (id,e) => {
-    console.log(id)
-  }
-
-  const renderUserSelectTreeNodes = data => {
-    return data.map(item => {
-      if (item.children) {
-        return (
-          <TreeNode
-            title={item.title}
-            key={item.key}
-            value={item.manage || item.value}
-            dataRef={item}
-            selectable={item.selectable}
-          >
-            {renderUserSelectTreeNodes(item.children)}
-          </TreeNode>
-        );
-      }
-      return (
-        <TreeNode
-          title={item.title}
-          key={item.ID}
-          value={item.manage || item.value}
-          selectable={item.selectable}
-        />
-      );
-    });
-  };
-
+  
   return (
     <Modal
       title="转执行"
@@ -66,44 +34,37 @@ function ExecutionModal(props) {
       onCancel={onClose}
       onOk={handleOk}
     >
-      <Form labelCol={{ span: 5 }} wrapperCol={{ span: 15 }}>
-        <Form.Item label="项目经理">
-          {form.getFieldDecorator('managerID', {
-            rules: [
-              {
-                required: true,
-                message: '请选择项目经理',
-              },
-            ],
-          })(
-            <TreeSelect
-              showSearch
-              allowClear
-              style={{ width: '100%' }}
-              placeholder="请选择项目经理"
-              multiple={false}
-              filterTreeNode={(input, option) => {
-                return option.props.title === input;
-              }}
-              onChange={onChangeManager}
-            >
-              {renderUserSelectTreeNodes(depUserTree)}
-            </TreeSelect>
-          )}
+      <Form labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} form={form}>
+        <Form.Item
+          label="项目经理"
+          name="managerID"
+          rules={[{ required: true, message: '请选择项目经理' }]}
+        >
+          <TreeSelect
+            showSearch
+            allowClear
+            style={{ width: '100%' }}
+            placeholder="请选择项目经理"
+            multiple={false}
+            filterTreeNode={(input, option) => {
+              return option.props.title === input;
+            }}
+            treeData={depUserTree}
+          />
         </Form.Item>
 
-        <Form.Item label="合同状态">
-          {form.getFieldDecorator('contractStatus', {
-            rules: [{ required: true, message: '请选择合同状态' }],
-          })(
-            <Select style={{ width: '100%' }}>
-              <Option key={0}>无合同</Option>
-              <Option key={1}>有合同</Option>
-            </Select>
-          )}
+        <Form.Item
+          label="合同状态"
+          name="contractStatus"
+          rules={[{ required: true, message: '请选择合同状态' }]}
+        >
+          <Select style={{ width: '100%' }}>
+            <Option key={0}>无合同</Option>
+            <Option key={1}>有合同</Option>
+          </Select>
         </Form.Item>
       </Form>
     </Modal>
   );
 }
-export default connect()(Form.create()(ExecutionModal));
+export default connect()(ExecutionModal);

+ 113 - 64
src/pages/PurchaseAdmin/PurchaseList/Approval/List.js

@@ -1,7 +1,5 @@
 import React, { useState, useEffect } from 'react';
-import { Form } from '@ant-design/compatible';
-import '@ant-design/compatible/assets/index.css';
-import { Table, Button, Select, Divider, Modal, Popover, Input, Checkbox } from 'antd';
+import { Table, Button, Form, Select, Divider, Modal, Popover, Input, Checkbox } from 'antd';
 import moment from 'moment';
 import router from 'umi/router';
 import styles from './List.less';
@@ -23,6 +21,14 @@ const STATUS = [
     value: 1,
     label: '转执行',
   },
+  {
+    value: 2,
+    label: '转运营',
+  },
+  {
+    value: 3,
+    label: '转质保',
+  },
 ];
 
 function List(props) {
@@ -34,10 +40,10 @@ function List(props) {
     currentUser,
     dispatch,
     loading,
-    form,
     depUserTree,
     member,
   } = props;
+  const [form] = Form.useForm();
   const [addVisible, setAddVisible] = useState(false);
   const [detailVisible, setDetailVisible] = useState(false);
   const [executionVisible, setExecutionVisible] = useState(false);
@@ -83,22 +89,24 @@ function List(props) {
     */
     {
       title: '流程',
-      dataIndex: 'FlowInfo.name',
+      dataIndex: ['FlowInfo', 'name'],
     },
     {
       title: '状态',
       dataIndex: 'project_status',
       render: project_status => {
-        return project_status === 0 ? <>售前</> : <>转执行</>;
+        // return project_status === 0 ? <>售前</> : <>转执行</>;
         //若添加其他状态则启用以下switch case:
-        /*
-        switch (project_status){
+        switch (project_status) {
           case 0:
             return <>售前</>;
           case 1:
             return <>转执行</>;
+          case 2:
+            return <>转运营</>;
+          case 3:
+            return <>转质保</>;
         }
-        */
       },
     },
     {
@@ -132,14 +140,20 @@ function List(props) {
       },
     },
     {
-      title: '创建人',
-      dataIndex: 'AuthorUser.CName',
+      title: '售前项目经理',
+      dataIndex: 'AuthorUser',
+      render: AuthorUser => (AuthorUser ? AuthorUser.CName : '-'),
     },
     {
       title: '创建时间',
       dataIndex: 'c_time',
       render: c_time => moment(c_time).format('YYYY.MM.DD'),
     },
+    {
+      title: '执行经理',
+      dataIndex: 'Leader',
+      render: Leader => (Leader ? Leader.CName : '-'),
+    },
     {
       title: '操作',
       render: record => renderEditBtns(record),
@@ -147,17 +161,16 @@ function List(props) {
   ];
 
   const handleSearch = () => {
-    form.validateFields((error, { projectCode, projectStatus }) => {
-      // console.log(error,values);
-      let params = {};
-      params.project_code = projectCode;
-      params.project_status = projectStatus;
-      params.currentPage = 1;
+    const { projectName, projectCode, projectStatus } = form.getFieldsValue();
+    let params = {};
+    params.project_name = projectName;
+    params.project_code = projectCode?.toUpperCase();
+    params.project_status = projectStatus;
+    params.currentPage = 1;
 
-      dispatch({
-        type: 'approval/queryApproval',
-        payload: params,
-      });
+    dispatch({
+      type: 'approval/queryApproval',
+      payload: params,
     });
   };
 
@@ -176,30 +189,30 @@ function List(props) {
 
   const renderSearch = () => {
     return (
-      <Form layout="inline">
-        <Form.Item label="项目编号">
-          {form.getFieldDecorator('projectCode', {
-            initialValue: null,
-          })(<Input style={{ width: 200 }}></Input>)}
+      <Form
+        form={form}
+        layout="inline"
+        initialValues={{ projectName: null, projectCode: null, projectStatus: null }}
+      >
+        <Form.Item label="项目名称" name="projectName">
+          <Input style={{ width: 200 }} />
+        </Form.Item>
+        <Form.Item label="项目编号" name="projectCode">
+          <Input style={{ width: 200 }} />
         </Form.Item>
-        <Form.Item label="状态">
-          {form.getFieldDecorator('projectStatus', {
-            initialValue: null,
-          })(
-            <Select
-              allowClear
-              showSearch
-              style={{ width: 120 }}
-              filterOption={(input, option) =>
-                option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
-              }
-            >
-              <Option value={null}>全部</Option>
-              {STATUS.map(item => (
-                <Option key={item.value}>{item.label}</Option>
-              ))}
-            </Select>
-          )}
+        <Form.Item label="状态" name="projectStatus">
+          <Select
+            showSearch
+            style={{ width: 120 }}
+            filterOption={(input, option) =>
+              option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }
+          >
+            <Option value={null}>全部</Option>
+            {STATUS.map(item => (
+              <Option key={item.value}>{item.label}</Option>
+            ))}
+          </Select>
         </Form.Item>
         <Form.Item>
           <Button type="primary" loading={loading} onClick={handleSearch}>
@@ -361,39 +374,75 @@ function List(props) {
       </>
     );
     let { audit_status, project_status, type_id } = record;
+    //权限审核
+    let canEdit = () => {
+      if (currentUser.IsSuper) return true;
+      switch (audit_status) {
+        case 0:
+          return currentUser.ID == record.author;
+        case 1:
+          return false;
+        case 2:
+          if (project_status == 0) return currentUser.ID == record.author;
+          if (project_status == 1) return currentUser.ID == record.LeaderId;
+          return false;
+        case 3:
+          switch (project_status) {
+            case 0:
+              return currentUser.ID == record.author;
+            case 1:
+              return currentUser.ID == record.LeaderId;
+            case 2:
+              return currentUser.ID == record.LeaderId || currentUser.ID == record.opt_manager_id;
+            case 3:
+              return currentUser.ID == record.LeaderId || currentUser.ID == record.wty_manager_id;
+          }
+          return false;
+      }
+    };
     let toReturn = [];
-    if (type_id != 7) dividerPush(detailBtn, toReturn);
+    dividerPush(detailBtn, toReturn);
     switch (audit_status) {
       //未提交
       case 0:
-        if (currentUser.ID == record.author || currentUser.IsSuper) dividerPush(editBtn, toReturn);
+        canEdit() && dividerPush(editBtn, toReturn);
         break;
       //审核中
       case 1:
         break;
       //审核拒绝
       case 2:
-        if (project_status == 0 && (currentUser.ID == record.author || currentUser.IsSuper))
-          dividerPush(editBtn, toReturn);
-        else if (
-          project_status == 1 &&
-          (currentUser.ID == record.LeaderId || currentUser.IsSuper)
-        ) {
+        if (project_status == 0 && canEdit()) dividerPush(editBtn, toReturn);
+        else if (project_status == 1 && canEdit()) {
           dividerPush(memberBtn, toReturn);
           dividerPush(statusBtn, toReturn);
         }
         break;
       //审核通过
       case 3:
-        if (project_status == 0 && (currentUser.ID == record.author || currentUser.IsSuper)) {
-          dividerPush(memberBtn, toReturn);
-          dividerPush(executionBtn, toReturn);
-        } else if (
-          project_status == 1 &&
-          (currentUser.ID == record.LeaderId || currentUser.IsSuper)
-        ) {
-          dividerPush(memberBtn, toReturn);
-          dividerPush(statusBtn, toReturn);
+        switch (project_status) {
+          //售前
+          case 0:
+            if (canEdit()) {
+              dividerPush(memberBtn, toReturn);
+              dividerPush(executionBtn, toReturn);
+            }
+            break;
+          //转执行
+          case 1:
+            if (canEdit()) {
+              dividerPush(memberBtn, toReturn);
+              dividerPush(statusBtn, toReturn);
+            }
+            break;
+          //转运营
+          case 2:
+            canEdit() && dividerPush(memberBtn, toReturn);
+            break;
+          //转质保
+          case 3:
+            canEdit() && dividerPush(memberBtn, toReturn);
+            break;
         }
         break;
     }
@@ -443,6 +492,8 @@ function List(props) {
         onChange={queryList}
       />
       <ApprovalModal
+        currentUser={currentUser}
+        depUserTree={depUserTree}
         loading={loading}
         industryList={industryList}
         flowList={flowList}
@@ -464,7 +515,6 @@ function List(props) {
       <ExecutionModal
         depUserTree={depUserTree}
         loading={loading}
-        flowList={flowList}
         visible={executionVisible}
         currentItem={currentItem}
         onOk={() => setExecutionVisible(false)}
@@ -481,7 +531,6 @@ function List(props) {
       <QualityOperateModal
         depUserTree={depUserTree}
         loading={loading}
-        flowList={flowList}
         visible={qualityOperateVisible}
         currentItem={currentItem}
         onOk={() => setQualityOperateVisible(false)}
@@ -501,4 +550,4 @@ export default connect(({ approval, user, loading }) => ({
   loading: loading.models.approval,
   depUserTree: approval.depUserTree,
   member: approval.member,
-}))(Form.create()(List));
+}))(List);

+ 33 - 81
src/pages/PurchaseAdmin/PurchaseList/Approval/MemberModal.js

@@ -1,40 +1,11 @@
 import React, { useState, useEffect } from 'react';
-import { Form } from '@ant-design/compatible';
-import '@ant-design/compatible/assets/index.css';
-import { Modal, TreeSelect, Table, Button } from 'antd';
+import { Form, Modal, TreeSelect, Table, Button, Input } from 'antd';
 import { connect } from 'dva';
 const { TreeNode } = TreeSelect;
 
-//测试用临时数据
-const tempData = [
-  {
-    name: 'aaa',
-    phone: '111',
-    email: 'aaa@a.com',
-  },
-  {
-    name: 'bbb',
-    phone: '222',
-    email: 'bbb@b.com',
-  },
-  {
-    name: 'ccc',
-    phone: '333',
-    email: 'ccc@c.com',
-  },
-];
-
 function MemberModal(props) {
-  const {
-    visible,
-    onClose,
-    form,
-    currentItem,
-    loading,
-    depUserTree,
-    dataSource,
-    dispatch,
-  } = props;
+  const { visible, onClose, currentItem, loading, depUserTree, dataSource, dispatch } = props;
+  const [form] = Form.useForm();
   const [currentMember, setCurrentMember] = useState({});
 
   const columns = [
@@ -53,36 +24,14 @@ function MemberModal(props) {
     {
       title: '操作',
       //移除按钮,此处应传入当前成员的数据
-      render: member => <a onClick={() => onDelete(member)}>移除</a>,
+      render: member =>
+        member.ID != currentItem.author &&
+        member.ID != currentItem.LeaderId &&
+        member.ID != currentItem.wty_manager_id &&
+        member.ID != currentItem.opt_manager_id && <a onClick={() => onDelete(member)}>移除</a>,
     },
   ];
 
-  const renderUserSelectTreeNodes = data => {
-    return data.map(item => {
-      if (item.children) {
-        return (
-          <TreeNode
-            title={item.title}
-            key={item.key}
-            value={item.value}
-            dataRef={item}
-            selectable={item.selectable}
-          >
-            {renderUserSelectTreeNodes(item.children)}
-          </TreeNode>
-        );
-      }
-      return (
-        <TreeNode
-          title={item.title}
-          key={item.ID}
-          value={item.value}
-          selectable={item.selectable}
-        />
-      );
-    });
-  };
-
   const onDelete = item => {
     Modal.confirm({
       title: '移除成员',
@@ -100,17 +49,19 @@ function MemberModal(props) {
   };
 
   const handleAddMember = () => {
-    form.validateFields((error, { memberID }) => {
-      if (error) return;
+    form.validateFields().then(({ memberID }) => {
       dispatch({
         type: 'approval/addMember',
         payload: {
           project_code_id: currentItem.id,
-          user_id: memberID,
+          user_id: Number(memberID.split('||')[0]),
+        },
+        callback: () => {
+          form.resetFields();
         },
       });
     });
-    form.resetFields();
+    // form.resetFields();
   };
 
   return (
@@ -130,24 +81,25 @@ function MemberModal(props) {
         width="100%"
         style={{ marginBottom: 20 }}
         layout="inline"
+        form={form}
       >
-        <Form.Item label="添加成员">
-          {form.getFieldDecorator('memberID', {
-            initialValue: null,
-          })(
-            <TreeSelect
-              showSearch
-              allowClear
-              style={{ width: 240 }}
-              placeholder="请选择项目成员"
-              multiple={false}
-              filterTreeNode={(input, option) => {
-                return option.props.title === input;
-              }}
-            >
-              {renderUserSelectTreeNodes(depUserTree)}
-            </TreeSelect>
-          )}
+        <Form.Item
+          label="添加成员"
+          name="memberID"
+          initialValue={null}
+          rules={[{ required: true, message: '请选择成员' }]}
+        >
+          <TreeSelect
+            showSearch
+            allowClear
+            style={{ width: 240 }}
+            placeholder="请选择项目成员"
+            multiple={false}
+            treeData={depUserTree}
+            filterTreeNode={(input, option) => {
+              return option.props.title === input;
+            }}
+          />
         </Form.Item>
         <Form.Item>
           <Button type="primary" loading={loading} onClick={handleAddMember}>
@@ -160,4 +112,4 @@ function MemberModal(props) {
   );
 }
 
-export default connect()(Form.create()(MemberModal));
+export default connect()(MemberModal);

+ 39 - 75
src/pages/PurchaseAdmin/PurchaseList/Approval/QualityOperateModal.js

@@ -1,7 +1,5 @@
 import React, { useState, useEffect } from 'react';
-import { Form } from '@ant-design/compatible';
-import '@ant-design/compatible/assets/index.css';
-import { Select, Modal, Input, TreeSelect } from 'antd';
+import { Form, Select, Modal, Input, TreeSelect } from 'antd';
 import { connect } from 'dva';
 const { Option } = Select;
 const { TreeNode } = TreeSelect;
@@ -11,59 +9,30 @@ function QualityOperateModal(props) {
     visible,
     onOk,
     onClose,
-    form,
     currentItem,
-    flowList = [],
     loading,
     depUserTree,
     dispatch,
     qualityOperate,
   } = props;
+  const [form] = Form.useForm();
 
-  //   const handleOk = () => {
-  //     form.validateFields((err, {managerID, contractStatus}) => {
-  //       if (err) return;
-  //       const [exe_manager_id,dep_id] = managerID.split("||")
-  //       dispatch({
-  //         type: 'approval/startExecution',
-  //         payload: {
-  //           project_code_id: currentItem.id,
-  //           with_contract: Number(contractStatus),
-  //           dep_id: Number(dep_id),
-  //           exe_manager_id: Number(exe_manager_id),
-  //         },
-  //         callback: () => onOk(),
-  //       })
-  //     });
-  //   };
-
-  const onChangeManager = (id, e) => {
-    console.log(id);
-  };
-
-  const renderUserSelectTreeNodes = data => {
-    return data.map(item => {
-      if (item.children) {
-        return (
-          <TreeNode
-            title={item.title}
-            key={item.key}
-            value={item.manage || item.value}
-            dataRef={item}
-            selectable={item.selectable}
-          >
-            {renderUserSelectTreeNodes(item.children)}
-          </TreeNode>
-        );
-      }
-      return (
-        <TreeNode
-          title={item.title}
-          key={item.ID}
-          value={item.manage || item.value}
-          selectable={item.selectable}
-        />
-      );
+  const handleOk = () => {
+    form.validateFields().then(({ managerID }) => {
+      const [manager_id, dep_id] = managerID.split('||');
+      let params = {};
+      params.type = qualityOperate ? 'approval/startOperate' : 'approval/startQuality';
+      params.payload = {
+        project_code_id: currentItem.id,
+        dep_id: Number(dep_id),
+      };
+      qualityOperate
+        ? (params.payload.opt_manager_id = Number(manager_id))
+        : (params.payload.wty_manager_id = Number(manager_id));
+      dispatch({
+        ...params,
+        callback: () => onOk(),
+      });
     });
   };
 
@@ -75,35 +44,30 @@ function QualityOperateModal(props) {
       destroyOnClose
       visible={visible}
       onCancel={onClose}
-      onOk={onOk}
+      onOk={handleOk}
     >
-      <Form labelCol={{ span: 5 }} wrapperCol={{ span: 15 }}>
-        <Form.Item label={qualityOperate ? '运营经理' : '质保经理'}>
-          {form.getFieldDecorator('managerID', {
-            rules: [
-              {
-                required: true,
-                message: qualityOperate ? '请选择运营经理' : '请选择质保经理',
-              },
-            ],
-          })(
-            <TreeSelect
-              showSearch
-              allowClear
-              style={{ width: '100%' }}
-              placeholder={qualityOperate ? '请选择运营经理' : '请选择质保经理'}
-              multiple={false}
-              filterTreeNode={(input, option) => {
-                return option.props.title === input;
-              }}
-              onChange={onChangeManager}
-            >
-              {renderUserSelectTreeNodes(depUserTree)}
-            </TreeSelect>
-          )}
+      <Form labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} form={form}>
+        <Form.Item
+          label={qualityOperate ? '运营经理' : '质保经理'}
+          name="managerID"
+          rules={[
+            { required: true, message: qualityOperate ? '请选择运营经理' : '请选择质保经理' },
+          ]}
+        >
+          <TreeSelect
+            showSearch
+            allowClear
+            style={{ width: '100%' }}
+            placeholder={qualityOperate ? '请选择运营经理' : '请选择质保经理'}
+            multiple={false}
+            filterTreeNode={(input, option) => {
+              return option.props.title === input;
+            }}
+            treeData={depUserTree}
+          ></TreeSelect>
         </Form.Item>
       </Form>
     </Modal>
   );
 }
-export default connect()(Form.create()(QualityOperateModal));
+export default connect()(QualityOperateModal);

+ 24 - 7
src/pages/PurchaseAdmin/PurchaseList/Approval/models/approval.js

@@ -13,6 +13,8 @@ import {
   addMember,
   queryMember,
   startExecution,
+  startOperate,
+  startQuality,
   deleteMember,
 } from '@/services/approval';
 import { message } from 'antd';
@@ -27,17 +29,17 @@ function getDepUserTree(data) {
 
   if (data.children) {
     data.children.forEach(item => {
-      getDepUserTree(item, false);
+      getDepUserTree(item);
     });
   }
 
   if (data.Users && data.Users.length !== 0) {
     data.Users.forEach(item => {
       item.title = item.CName;
-      item.key = item.ID;
-      item.value = item.ID;
-      item.manage =  item.ID + "||" + data.ID
+      item.key = item.ID + '||' + data.ID;
+      item.value = item.ID + '||' + data.ID;
       item.selectable = true;
+      item.DepId = data.ID;
       data.children.push(item);
     });
   }
@@ -130,7 +132,6 @@ export default {
       });
     },
     *queryMember({ payload }, { call, put }) {
-      console.log(111);
       const { data } = yield call(queryMember, payload);
       if (!data) return;
       yield put({
@@ -156,10 +157,8 @@ export default {
     },
     *createApproval({ payload, callback }, { call, put }) {
       const res = yield call(createApproval, payload);
-      console.log(res);
       if (res) {
         const { data } = res;
-        console.log(data);
         callback && callback();
         message.success('创建成功');
         yield put({
@@ -223,6 +222,24 @@ export default {
         type: 'queryApproval',
       });
     },
+    *startOperate({ payload, callback }, { call, put }) {
+      const res = yield call(startOperate, payload);
+      if (!res) return;
+      callback && callback();
+      message.success('转运营送审成功');
+      yield put({
+        type: 'queryApproval',
+      });
+    },
+    *startQuality({ payload, callback }, { call, put }) {
+      const res = yield call(startQuality, payload);
+      if (!res) return;
+      callback && callback();
+      message.success('转质保送审成功');
+      yield put({
+        type: 'queryApproval',
+      });
+    },
   },
 
   reducers: {

+ 502 - 72
src/pages/PurchaseAdmin/PurchaseList/Detail/CommitAuditModal.js

@@ -1,66 +1,116 @@
-import React, { useEffect, useState, useRef, useMemo } from 'react';
-import { Form } from '@ant-design/compatible';
+import React, { useEffect, useState, useRef, useMemo, useCallback } from 'react';
 import '@ant-design/compatible/assets/index.css';
-import { Modal, Input, Select, message } from 'antd';
+import {
+  Modal,
+  Input,
+  Select,
+  message,
+  Cascader,
+  Form,
+  Tabs,
+  Row,
+  Col,
+  Empty,
+  Button,
+  Steps,
+  Popover,
+} from 'antd';
+import { PlusOutlined } from '@ant-design/icons';
 import { connect } from 'dva';
+import { isArray, result, set } from 'lodash';
+import { useForm } from 'rc-field-form';
+import { async } from '@antv/x6/lib/registry/marker/async';
+import AuditDetailed from './AuditDetailed';
+import AuditFlow from './AuditFlow';
+import { queryDingSchema, queryProcessFlows } from '@/services/boom';
+import { Form as Form3x } from '@ant-design/compatible';
+import { getCurrentUser } from '@/utils/authority';
+import DDCode from '@/components/DDComponents/DDCode';
+import { uploadFile, queryUserListByRoleID } from '@/services/boom';
+import ApprovalProcess from './ApprovalProcess';
 
 const { TextArea } = Input;
 const { Option } = Select;
+const { TabPane } = Tabs;
+const { Step } = Steps;
 
 // 提交
 function CommitAuditModal(props) {
-  const { visible, onClose, onOk, form, loading, version, versionList, flowDetail } = props;
+  const {
+    dispatch,
+    visible,
+    onClose,
+    onOk,
+    loading,
+    version,
+    versionList,
+    flowDetail,
+    currentUser,
+    luckysheet,
+    userList,
+  } = props;
   const [auditId, setAuditId] = useState();
-  const handleOk = () => {
-    form.validateFields((err, fieldsValue) => {
-      if (err) return;
-      let approvalNode = flowDetail.nodes.find?.(item => item.Id == fieldsValue.node_id);
-      let serviceNode = flowDetail.nodes.find?.(
-        item => item.Id == fieldsValue.next_template_node_id
-      );
-      let params = {
-        desc: fieldsValue.desc,
-        // 审核流程id
-        flow_id: approvalNode?.flow_id || 0,
-        node_level_id: approvalNode?.flow_id ? 1 : 0,
-
-        id: version.id,
-        project_id: version.project_id,
-        cur_template_node_id: version.template_node_id * 1, // 当前节点
-        template_node_id: approvalNode?.Id, // 将要流转的节点
-        next_template_node_id: serviceNode.Id * 1, // 审核完成后的业务节点
-
-        // 模板id.一致就行
-        template_id: version.template_id,
-        cur_template_id: version.template_id,
-        next_template_id: version.template_id,
-      };
-      if (approvalNode?.Id) {
-        if (!approvalNode?.flow_id) {
-          message.error('审批节点未绑定审批流程!请联系管理员。');
-          return;
-        }
-      }
+  const [data, setData] = useState([]);
+  const [length, setLength] = useState(1);
+  const [formData, setFromData] = useState({});
+  const [auditList, setAuditList] = useState([]); //用于创建Tabs表单
+  const [formComponentValues, setFormComponentValues] = useState({}); //用于创建Tabs表单
+  const [form] = Form.useForm();
+  const [approvalProcess, setApprovalProcess] = useState({});
+  const [selectUserList, setSelectUserList] = useState([]);
+  const [curNodeIdx, setCurNodeIdx] = useState(-1);
 
-      // // 判断业务节点是否允许多清单
-      // if (!serviceNode.muti_version) {
-      //   //audit_status=4 表示为清单推进后留存的副本.不计入多清单计算
-      //   let serviceVersion = versionList.find(
-      //     item => item.audit_status != 4 && item.template_node_id == serviceNode.Id
-      //   );
-      //   if (serviceVersion) {
-      //     message.error(
-      //       `流转失败!业务节点【${serviceNode.label}】为单清单节点。已存在清单【${serviceVersion.version_name}】。`
-      //     );
-      //     return;
-      //   }
-      // }
-      setAuditId();
-      onOk(params);
-    });
+  useEffect(() => {
+    if (!visible) return;
+    const { edges, nodes } = flowDetail;
+    let Id = version.template_node_id;
+    const currentId = flowDetail.nodes.find?.(item => item.Id == Id)?.node_id;
+    const data = treeData(currentId);
+    if (data.length <= 0) setAuditId(currentId);
+    setData(data);
+  }, [auditId, version.template_node_id, visible]);
+
+  useEffect(() => {
+    form.resetFields();
+    setAuditList([]);
+  }, [visible]);
+
+  const treeData = currentId => {
+    const list = getNextNodes(currentId, 'custom-circle');
+    const fun = nodes => {
+      const re = nodes?.forEach((item, idx) => {
+        const data = getNextNodes(item.Id, 'custom-circle');
+        if (data || data.length > 0) list.push(...data);
+        fun(data);
+      });
+    };
+    fun(list);
+
+    const fun2 = list => {
+      const parents = list.filter(item => list.findIndex(node => node.Id == item.parentId) == -1);
+      let translator = (parents, children) => {
+        setLength(length + 1);
+        parents.forEach(parent => {
+          children.forEach((current, index) => {
+            if (current.parentId === parent.Id) {
+              let temp = JSON.parse(JSON.stringify(children));
+              temp.splice(index, 1);
+              translator([current], temp);
+              if (!parent.children.find(item => item.Id == current.Id))
+                parent.children.push(current);
+            }
+          });
+        });
+      };
+      translator(parents, list);
+      return parents;
+    };
+    return fun2(list);
   };
+
   const currentNodeId = useMemo(() => {
     let Id = version.template_node_id;
+    setAuditId(currentNodeId);
     return flowDetail.nodes.find?.(item => item.Id == Id)?.node_id;
   }, [flowDetail, version]);
   /**
@@ -72,16 +122,34 @@ function CommitAuditModal(props) {
   const getNextNodes = (currentId, type) => {
     const { edges, nodes } = flowDetail;
     if (!currentId) return [];
+    //删除虚线通向的节点
+    // let targetIds = edges
+    //   .filter(edge => {
+    //     let line = edge.attrs?.line?.strokeDasharray?.split(' ');
+    //     return edge.source.cell == currentId && line && line[0] == '0';
+    //   })
+    //   .map(item => item.target.cell);
     let targetIds = edges
       .filter(edge => edge.source.cell == currentId)
       .map(item => item.target.cell);
+
+    edges.filter(edge => edge.source.cell == currentId);
     let auditNodes = nodes.filter(node => {
       if (type && node.name != type) {
         return false;
       }
       return targetIds.indexOf(node.id) != -1;
     });
-    return auditNodes || [];
+    const result = auditNodes.map(item => {
+      return {
+        label: item.label,
+        value: item.Id,
+        Id: item.node_id,
+        parentId: currentId,
+        children: [],
+      };
+    });
+    return result || [];
   };
 
   const changeAudit = id => {
@@ -89,44 +157,406 @@ function CommitAuditModal(props) {
     setAuditId(node.node_id);
   };
 
+  const onChange = value => {
+    changeAudit(value[value.length - 1]);
+    setAuditListFun();
+  };
+
+  const getReComputeAudit = (items, changedValues) => {
+    const id = Object.keys(changedValues)[0];
+    const formItem = items?.find(item => item.props.id == id);
+    if (formItem && formItem.props?.required) return true;
+    return false;
+  };
+
+  //填写表单实时计算审批流程
+  const advanceSubmit = async () => {
+    console.log('重重新计算审批流程');
+    var fieldsValue = await form.validateFields();
+    let hasFlowId = true; //是否都绑定审批节点
+
+    let result = Object.values(fieldsValue)
+      .map(item => {
+        if (item && Array.isArray(item)) return item;
+      })
+      .filter(item => item);
+    const formList = await getFromData(result);
+    let params = {
+      desc: fieldsValue.desc,
+      // 审核流程id
+      flow_id: 0,
+      node_level_id: 0,
+      id: version.id,
+      project_id: version.project_id,
+      cur_template_node_id: version.template_node_id * 1, // 当前节点
+      next_template_node_id: 0, // 审核完成后的业务节点
+      template_node_id: null, // 将要流转的节点审批节点
+      flow_path: null, //审批节点数组
+      // 模板id.一致就行
+      template_id: version.template_id,
+      cur_template_id: version.template_id,
+      next_template_id: version.template_id,
+      form_list: formList,
+    };
+
+    const ddd = {
+      '778': [
+        { type: 'user', value: 448, origin: 0 },
+        { type: 'role', value: 82, origin: 0 },
+        { type: 'role', value: 61, origin: 0 },
+      ],
+      '791': [
+        { type: 'user', value: 448, origin: 0 },
+        { type: 'role', value: 82, origin: 0 },
+        { type: 'role', value: 61, origin: 0 },
+      ],
+      '792': [
+        { type: 'user', value: 448, origin: 0 },
+        { type: 'role', value: 82, origin: 0 },
+        { type: 'role', value: 29, origin: 0 },
+      ],
+    };
+
+    dispatch({
+      type: 'detail/advanceSubmitNextNode',
+      payload: params, //values,
+      callback: data => {
+        if (data) {
+          setApprovalProcess(data);
+        }
+        console.log('=======================================', data);
+      },
+    });
+  };
+
+  //处理tabs页
+  const setAuditListFun = async () => {
+    var fieldsValue = await form.validateFields();
+    let addAuditList = [];
+    let result = Object.values(fieldsValue)
+      .map(item => {
+        if (item && Array.isArray(item)) return item;
+      })
+      .filter(item => item)
+      .flat(Infinity);
+    let nodeList = [...new Set(result)]
+      .map(Id => {
+        return flowDetail.nodes.find?.(item => item.Id == Id);
+      })
+      .filter(item => item);
+    let flowIds = [...new Set(nodeList.map(item => item.flow_id))].join(',');
+    let data = await queryProcessFlows({ ids: flowIds });
+    if (data && data?.length > 0) {
+      let newlist = nodeList.map(node => {
+        let curData = data.find(item => item.id == node.flow_id);
+        let newItem = {
+          name: curData?.name,
+          nodeId: node.Id,
+          items: JSON.parse(curData.form_json),
+        };
+        return newItem;
+      });
+      addAuditList = [...addAuditList, ...newlist];
+    }
+    addAuditList.forEach((item, index) => {
+      let Components = Form3x.create({
+        onValuesChange: (props, changedValues, allValues) => {
+          const { items } = props;
+          formComponentValues[item.nodeId] = items
+            .map(item => {
+              const itemProps = item.props;
+              let val = allValues[itemProps.id];
+              if (!itemProps.label || val === '') return;
+              if (val instanceof Object) {
+                return {
+                  name: itemProps.label,
+                  id: itemProps.id,
+                  value: [...val],
+                };
+              } else if (allValues[itemProps.id]) {
+                return {
+                  name: itemProps.label,
+                  id: itemProps.id,
+                  value: [allValues[itemProps.id]] || undefined,
+                };
+              }
+            })
+            .filter(item => item);
+          if (getReComputeAudit(items, changedValues)) advanceSubmit();
+          console.log('==================================', { ...formComponentValues });
+          setFormComponentValues({ ...formComponentValues });
+        },
+      })(AuditDetailed);
+      item.FormComponents = <Components items={item.items} />;
+    });
+    setAuditList(addAuditList);
+  };
+
+  const getFromData = async idList => {
+    const data = formComponentValues;
+    const result = [];
+    //获取流转节点的层级关系
+    let len = 0;
+    let list = [];
+    idList.forEach(item => {
+      if (len < item.length) len = item.length;
+    });
+    for (let i = 0; i < len; i++) {
+      idList.forEach(item => {
+        if (item && item[i]) list.push(item[i]);
+      });
+    }
+    let firstList = [...new Set(list)];
+    // let attachment = await upload();
+    firstList.forEach(id => {
+      let approvalNode = flowDetail.nodes.find?.(item => item.Id == id);
+      let values = data[approvalNode.Id] || [];
+      const audit_list = approvalProcess[approvalNode.Id]?.map(item => {
+        if (item[0].type == 'role') return item[0].nowValue;
+        return item[0].value;
+      });
+      const formItem = {
+        flow_id: approvalNode.flow_id,
+        template_node_id: approvalNode.Id,
+        formComponentValues: [...values], //{ name: '附件', value: JSON.stringify(attachment) }
+        audit_list: audit_list || [],
+      };
+      result.push(JSON.stringify(formItem));
+    });
+    return result;
+  };
+
+  const getFlowPath = node => {
+    //[134, 135]
+    let itemData = {};
+    const Function = (curId, index) => {
+      if (!curId) return;
+      let data = {};
+      let approvalNode = flowDetail.nodes.find?.(item => item.Id == curId);
+      data.template_id = version.template_id;
+      data.flow_id = approvalNode?.flow_id || 0;
+      data.node_level_id = approvalNode?.flow_id ? 1 : 0;
+      data.template_node_id = approvalNode?.Id;
+      index++;
+      if (approvalNode?.Id) {
+        if (!approvalNode?.flow_id) {
+          hasFlowId = false;
+        }
+      }
+      const res = Function(node[index], index);
+      if (res) {
+        data.flow_path = [res];
+      }
+      return data;
+    };
+    itemData = Function(node[0], 0);
+    return itemData;
+  };
+
+  const onFinish = async () => {
+    console.log(approvalProcess);
+    const isOk = Object.values(approvalProcess).every(item => {
+      console.log(item);
+      return item.every(cur => {
+        if (cur[0].type == 'role') return cur[0].nowValue;
+        return true;
+      });
+    });
+    if (!isOk) {
+      message.error('请选择审批人。');
+      return;
+    }
+    var fieldsValue = await form.validateFields();
+    let hasFlowId = true; //是否都绑定审批节点
+    const getFlowPath = node => {
+      //[134, 135]
+      let itemData = {};
+      const Function = (curId, index) => {
+        if (!curId) return;
+        let data = {};
+        let approvalNode = flowDetail.nodes.find?.(item => item.Id == curId);
+        data.template_id = version.template_id;
+        data.flow_id = approvalNode?.flow_id || 0;
+        data.node_level_id = approvalNode?.flow_id ? 1 : 0;
+        data.template_node_id = approvalNode?.Id;
+        index++;
+        if (approvalNode?.Id) {
+          if (!approvalNode?.flow_id) {
+            hasFlowId = false;
+          }
+        }
+        const res = Function(node[index], index);
+        if (res) {
+          data.flow_path = [res];
+        }
+        return data;
+      };
+      itemData = Function(node[0], 0);
+      return itemData;
+    };
+    let result = Object.values(fieldsValue)
+      .map(item => {
+        if (item && Array.isArray(item)) return item;
+      })
+      .filter(item => item);
+    let serviceNode = flowDetail.nodes.find?.(item => item.Id == fieldsValue.next_template_node_id);
+
+    if (!serviceNode) {
+      message.error('请选择需要流转的业务节点。');
+      return;
+    }
+
+    const flowPath = result.map(item => getFlowPath(item));
+    const formList = await getFromData(result);
+    let params = {
+      desc: fieldsValue.desc,
+      // 审核流程id
+      // flow_id: approvalNode?.flow_id || 0,
+      // node_level_id: approvalNode?.flow_id ? 1 : 0,
+
+      id: version.id,
+      project_id: version.project_id,
+      cur_template_node_id: version.template_node_id * 1, // 当前节点
+      next_template_node_id: serviceNode.Id * 1, // 审核完成后的业务节点
+      // template_node_id: result[0][0], // 将要流转的节点审批节点
+      // flow_path:flow_path, //审批节点数组
+      // 模板id.一致就行
+      template_id: version.template_id,
+      cur_template_id: version.template_id,
+      next_template_id: version.template_id,
+    };
+    if (result.length <= 0) {
+      //直接走业务节点
+    } else if (result.length <= 1 && result[0]?.length <= 1) {
+      //单个审批节点
+      let approvalNode = flowDetail.nodes.find?.(item => item.Id == result[0][0]);
+      params.flow_id = approvalNode?.flow_id || 0;
+      params.node_level_id = approvalNode?.flow_id ? 1 : 0;
+      params.template_node_id = result[0][0]; // 将要流转的节点审批节点
+      params.form_list = formList; //创建钉钉表单所需数据
+      if (approvalNode?.Id) {
+        if (!approvalNode?.flow_id) {
+          hasFlowId = false;
+        }
+      }
+    } else {
+      //多节点审批
+      params.template_node_id = result[0][0]; // 将要流转的节点审批节点
+      params.flow_path = flowPath;
+      params.form_list = formList; //创建钉钉表单所需数据
+    }
+    if (!hasFlowId) {
+      message.error('当前存在审批节点未绑定审批流程!请联系管理员。');
+      return;
+    }
+    onOk(params);
+  };
+  const CascaderNode = index => {
+    return (
+      <Form.Item
+        labelCol={{ span: 7 }}
+        wrapperCol={{ span: 15 }}
+        label={`审批节点${index + 1}`}
+        name={`circle${index}`}
+        key={`circle${index}`}
+      >
+        <Cascader style={{ width: '100%' }} options={data} onChange={onChange} />
+      </Form.Item>
+    );
+  };
+  const upload = async () => {
+    let blob = await luckysheet.current.getExcelBolb();
+    let formData = new FormData();
+    formData.append('userid', currentUser.DingUserId);
+    formData.append('file', new File([blob], `${version.version_name}_${version.version_no}.xlsx`));
+
+    try {
+      let res = await uploadFile(formData);
+      let data = JSON.parse(res.dentry);
+      return [
+        {
+          spaceId: String(data.spaceId),
+          fileName: data.name,
+          fileSize: String(data.spaceId),
+          fileType: data.extension,
+          fileId: data.id,
+        },
+      ];
+    } catch (error) {
+      message.error('附件上传失败');
+    }
+  };
+
   return (
     <Modal
       confirmLoading={loading}
       destroyOnClose
       title="提交流转目标"
+      width={1000}
       visible={visible}
       onCancel={() => {
         setAuditId();
         onClose();
       }}
-      onOk={handleOk}
+      onOk={onFinish}
     >
-      <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label="审批节点">
-        {form.getFieldDecorator('node_id')(
-          <Select style={{ width: '100%' }} onChange={changeAudit}>
-            {getNextNodes(currentNodeId, 'custom-circle').map(item => (
-              <Option key={item.Id}>{item.label}</Option>
-            ))}
-          </Select>
-        )}
-      </Form.Item>
-      <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label="业务节点">
-        {form.getFieldDecorator('next_template_node_id')(
+      <Form form={form}>
+        {data.map((item, idx) => (data.length == 1 ? CascaderNode('') : CascaderNode(idx)))}
+        <Form.Item
+          labelCol={{ span: 7 }}
+          wrapperCol={{ span: 15 }}
+          label="业务节点"
+          name="next_template_node_id"
+        >
           <Select style={{ width: '100%' }}>
-            {getNextNodes(auditId || currentNodeId, auditId ? 'custom-rect' : '').map(item => (
-              <Option key={item.Id}>{item.label}</Option>
+            {getNextNodes(data.length < 0 ? currentNodeId : auditId, 'custom-rect').map(item => (
+              <Option key={item.value}>{item.label}</Option>
             ))}
           </Select>
-        )}
-      </Form.Item>
-      <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label="备注信息">
-        {form.getFieldDecorator('desc')(<Input.TextArea />)}
-      </Form.Item>
+        </Form.Item>
+        <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label="备注信息" name="desc">
+          <Input.TextArea />
+        </Form.Item>
+        {/* <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label="授权码" name="ddCode">
+          <DDCode />
+          <Button onClick={upload}>上传文件</Button>
+        </Form.Item> */}
+      </Form>
+      <Tabs defaultActiveKey="1">
+        {auditList.map((item, idx) => (
+          <TabPane tab={item.name} key={`${idx}_${item.title}`}>
+            <Row>
+              <Col span={17}>{item.FormComponents}</Col>
+              <Col offset={1} span={4}>
+                {!formComponentValues[item.nodeId] || !approvalProcess[item.nodeId] ? (
+                  <Empty description="请先填写表单" />
+                ) : (
+                  <ApprovalProcess
+                    id={item.nodeId}
+                    approvalProcess={approvalProcess}
+                    onChange={setApprovalProcess}
+                  />
+                )
+                // <AuditFlow
+                //   processCode={item.formCode}
+                //   formComponentValues={formComponentValues[item.nodeId]}
+                //   direction={'vertical'}
+                //   deptId={'14237557'}
+                //   userId={currentUser.DingUserId || getCurrentUser()?.DingUserId}
+                // />
+                }
+              </Col>
+            </Row>
+          </TabPane>
+        ))}
+      </Tabs>
     </Modal>
   );
 }
 
-export default connect(({ xflow, detail }) => ({
+export default connect(({ xflow, detail, user }) => ({
   flowDetail: xflow.flowDetail,
   versionList: detail.versionList,
-}))(Form.create()(CommitAuditModal));
+  currentUser: user.currentUser,
+  userList: user.list,
+}))(CommitAuditModal);

+ 24 - 26
src/pages/PurchaseAdmin/PurchaseList/Detail/ExportModal.js

@@ -34,34 +34,34 @@ function CompareModal(props) {
   const [checkValue, setCheckValue] = useState([]);
   const [tabList, setTabList] = useState([]);
   const [active, setActive] = useState();
-  
-  useEffect(() => {
-      const list = []
-      if (sheet && sheet.length > 0) {
-        const sheetData = JSON.parse(JSON.stringify(sheet));
-        sheetData.forEach(item => {
-            let obj = {}
-            obj.name = item.name;
-            if(item.data && item.data[0]){
-                obj.list = item.data[0]?.filter( cur => cur);
-            }
-            obj.id = item.index;
-            list.push(obj)
-          });
 
-          setCheckValue([]);
-          setTabList(list);
-          setActive(list[0]?.id);
+  useEffect(() => {
+    const list = [];
+    if (sheet && sheet.length > 0) {
+      const sheetData = JSON.parse(JSON.stringify(sheet));
+      sheetData.forEach(item => {
+        let obj = {};
+        obj.name = item.name;
+        if (item.data && item.data[0]) {
+          obj.list = item.data[0]?.filter(cur => cur);
         }
-      },[sheet])
+        obj.id = item.index;
+        list.push(obj);
+      });
+
+      setCheckValue([]);
+      setTabList(list);
+      setActive(list[0]?.id);
+    }
+  }, [sheet]);
 
   const onChange = check => {
-      setCheckValue(check);
-      console.log(check)
+    setCheckValue(check);
+    console.log(check);
   };
 
   const handleOk = () => {
-    onOk(checkValue)
+    onOk(checkValue);
   };
 
   return (
@@ -77,13 +77,11 @@ function CompareModal(props) {
           {tabList.map(tab => (
             <TabPane tab={tab.name} key={tab.id}>
               <Row>
-                {tab.list.map(item => 
-                (
+                {tab.list.map(item => (
                   <Col span={8} key={item.m}>
                     <Checkbox value={item.v}>{item.v}</Checkbox>
                   </Col>
-                )
-                )}
+                ))}
               </Row>
             </TabPane>
           ))}
@@ -92,4 +90,4 @@ function CompareModal(props) {
     </Modal>
   );
 }
-export default CompareModal
+export default CompareModal;

+ 82 - 65
src/pages/PurchaseAdmin/PurchaseList/Detail/FilesModal.js

@@ -9,70 +9,87 @@ import moment from 'moment/moment';
 import PreviewFile from '@/components/PreviewFile';
 // 历史清单
 function FilesModal(props) {
-    const { visible, onClose, onUpload, data, uploadProps, DeleteFile, downloadFile, loading } = props;
-    const handleSelect = item => {
-        // onSelect(item);
-        // onClose();
-    };
-    const columns = [
-        {
-            title: '预览',
-            dataIndex: 'name',
-            render: (text, item) => {
-                return <PreviewFile name={item.name} src={item.url} />;
-            },
-        },
-        {
-            title: '上传时间',
-            dataIndex: 'c_time',
-            render: text => {
-                return text ? moment(text).format('YYYY年MM月DD日  HH:mm:ss') : null;
-            },
-        },
-        {
-            title: '上传人',
-            dataIndex: 'CreatorUser',
-            render: record => (record.CName || '')
-        },
-        {
-            title: '操作',
-            render: record => (
-                <>
-                    <a onClick={() => { downloadFile(record) }}>
-                        查看
-                    </a>
-                    <a onClick={() => {
-                        confirm({
-                            title: '提醒',
-                            content: '确认删除该文件,删除后无法复原',
-                            okText: '确认',
-                            cancelText: '取消',
-                            onOk() {
-                                DeleteFile(record.id);
-                            },
-                        });
+  const {
+    visible,
+    onClose,
+    onUpload,
+    data,
+    uploadProps,
+    DeleteFile,
+    downloadFile,
+    loading,
+  } = props;
+  const handleSelect = item => {
+    // onSelect(item);
+    // onClose();
+  };
+  const columns = [
+    {
+      title: '预览',
+      dataIndex: 'name',
+      render: (text, item) => {
+        return <PreviewFile name={item.name} src={item.url} />;
+      },
+    },
+    {
+      title: '上传时间',
+      dataIndex: 'c_time',
+      render: text => {
+        return text ? moment(text).format('YYYY年MM月DD日  HH:mm:ss') : null;
+      },
+    },
+    {
+      title: '上传人',
+      dataIndex: 'CreatorUser',
+      render: record => record.CName || '',
+    },
+    {
+      title: '操作',
+      render: record => (
+        <>
+          <a
+            onClick={() => {
+              downloadFile(record);
+            }}
+          >
+            查看
+          </a>
+          <a
+            onClick={() => {
+              confirm({
+                title: '提醒',
+                content: '确认删除该文件,删除后无法复原',
+                okText: '确认',
+                cancelText: '取消',
+                onOk() {
+                  DeleteFile(record.id);
+                },
+              });
+            }}
+            style={{ marginLeft: 10 }}
+          >
+            删除
+          </a>
+        </>
+      ),
+    },
+  ];
 
-                    }} style={{ marginLeft: 10 }}>删除</a>
-                </>
-            ),
-        },
-    ];
-
-    const onClick = item => {
-        // onClose();
-        // onSelect(item);
-    };
-    return (
-        <Modal title="附件列表" width="60%" onCancel={onClose} visible={visible} footer={false}>
-            <div>
-                <Upload {...uploadProps}>
-                    <Button type="primary" style={{ marginBottom: 20 }} loading={loading}>
-                        <UploadOutlined /> 上传文件
-                    </Button>
-                </Upload>
-            </div>
-            <Table rowKey="id" columns={columns} dataSource={data} loading={loading} />
-        </Modal>
-    );
+  const onClick = item => {
+    // onClose();
+    // onSelect(item);
+  };
+  return (
+    <Modal title="附件列表" width="60%" onCancel={onClose} visible={visible} footer={false}>
+      <div>
+        <Upload {...uploadProps}>
+          <Button type="primary" style={{ marginBottom: 20 }} loading={loading}>
+            <UploadOutlined /> 上传文件
+          </Button>
+        </Upload>
+      </div>
+      <Table rowKey="id" columns={columns} dataSource={data} loading={loading} />
+    </Modal>
+  );
 }
-export default FilesModal
+export default FilesModal;

+ 284 - 62
src/pages/PurchaseAdmin/PurchaseList/Detail/FlowModal.js

@@ -1,26 +1,46 @@
-import React, { useEffect, useState, useRef, useMemo } from 'react';
-import { Modal, Input, Select, List, Row, Col } from 'antd';
+import React, { useEffect, useState, useRef, useMemo, memo } from 'react';
+import { Modal, Input, Select, List, Row, Col, Table, message, Steps, Space, Button } from 'antd';
 import Flow from '@/components/Flow/index';
 import { connect } from 'dva';
 import { GetTokenFromUrl, getToken } from '@/utils/utils';
 import { MODELS, useXFlowApp, useModelAsync } from '@antv/xflow';
 import { CheckOutlined } from '@ant-design/icons';
-import { queryVserionByNode } from '@/services/boom';
+import { queryDingInstanceDetail, queryRecordSheet, queryVserionByNode } from '@/services/boom';
 import { async } from '@antv/x6/lib/registry/marker/async';
+import VersionModal from './VersionModal';
+import AuditFlow from './AuditFlow';
+
+const { Step } = Steps;
 
 const { TextArea } = Input;
 const localData = JSON.parse(localStorage.ggDetaiData || '{}');
+const PAGE_SIZE = 8;
 
 // 提交
 function FlowModal(props) {
-  const { visible, version, onClose, onChangeVersion, form, loading, flowDetail } = props;
+  const {
+    visible,
+    version,
+    onClose,
+    onChangeVersion,
+    form,
+    loading,
+    flowDetail,
+    dispatch,
+    isOut,
+    onCommit,
+    currentUser,
+  } = props;
   const [data, setData] = useState([]);
   const [nodeLoading, setNodeLoading] = useState(false);
-  // const app = useXFlowApp();
-  // const appRef = userRef()
+  const [pageSize, setPageSize] = useState(PAGE_SIZE);
+  const [stepsData, setStepsData] = useState([]);
+  const [versionVisible, setVersionVisible] = useState(false);
+  let token = getToken();
 
   const graphData = useMemo(() => {
-    let nodes = flowDetail.nodes.map(item => ({
+    if (!flowDetail) return;
+    let nodes = flowDetail.nodes?.map(item => ({
       ...item,
       isCheck: item.Id == version.template_node_id,
     }));
@@ -30,81 +50,281 @@ function FlowModal(props) {
     };
   }, [flowDetail, version.template_node_id]);
 
+  useEffect(() => {
+    if (!visible) updateSteps([]);
+  }, [visible, version]);
+
+  useEffect(() => {
+    if (stepsData.length <= 0) {
+      setPageSize(PAGE_SIZE);
+    } else {
+      setPageSize(PAGE_SIZE - stepsData.length);
+    }
+  }, [stepsData]);
+
   const handleSelectNode = async args => {
     let res;
     const id = args.nodeId || args.nodeIds[0];
     if (!id) return;
     let node = graphData.nodes.find(item => item.id == id);
-    // setData(node.version);
     setNodeLoading(true);
     try {
       res = await queryVserionByNode({ template_node_id: node.Id });
       let data = [];
-      Object.values(res.data.excel_version).map(arr => {
+      if (!res.data.excel_version_tree) setData([]);
+      res.data.excel_version_tree?.map(arr => {
         if (res.data.flow_id) {
-          data = [...data, arr[arr.length - 1]];
+          data = [...data, { ...arr, flow_id: res.data.flow_id }];
         } else {
-          data = [...data, ...arr];
+          data = [...data, arr];
         }
       });
+      data.sort((a, b) => b.id - a.id);
+      data.forEach((item, id) => {
+        //解决key报错问题
+        data[id].key = `${id}-${item.name}`;
+        item.isParent = true;
+      });
+      console.log(data);
       setData(data);
     } catch (error) {
       console.log(error);
     }
     setNodeLoading(false);
+    updateSteps([]);
+  };
+
+  const updateSteps = (data, curNodeId) => {
+    let newData = [];
+    let set = new Set();
+    data.forEach(item => set.add(item.template_node_id));
+    let list = [...set];
+    if (set.has(curNodeId)) {
+      set.delete(curNodeId);
+      list = [curNodeId, ...set];
+    }
+    let dataList = list.map(template_node_id => {
+      let itemDataList = data.filter(item => item.template_node_id == template_node_id);
+      let curid = 3;
+      let status = 'process';
+      itemDataList.forEach(item => {
+        if (item.audit_status != 3 && item.node_id <= curid) curid = item.node_id - 1;
+        if (item.audit_status == 2) status = 'error';
+      });
+      let curNode = flowDetail.nodes.find(item => item.Id == itemDataList[0].template_node_id);
+      const seqList = itemDataList[0].FlowInfo.FlowNodes.filter(
+        item => item.template_node_id == template_node_id
+      ).sort((a, b) => a.seq - b.seq);
+      let obj = {
+        status,
+        current: curid,
+        list: seqList,
+        name: curNode?.label || itemDataList[0].FlowInfo.name,
+      };
+      itemDataList.forEach((itemData, idx) => {
+        if (idx >= obj.list.length) return;
+        obj.list[idx].auditor = itemData.AuthorInfo.CName;
+      });
+      return obj;
+    });
+    console.log(dataList);
+    setStepsData(dataList);
+  };
+
+  // const updateSteps = async (data, curNodeId) => {
+  //   const dataList = [];
+  //   for (let i = 0; i < data.length; i++) {
+  //     let curNode = flowDetail.nodes.find(item => item.Id == data[i].template_node_id);
+  //     console.log(curNode);
+  //     const response = await queryDingInstanceDetail({
+  //       process_instance_id: data[i].ding_instance_id, //创建表单成功返回的id
+  //     });
+  //     if (response) {
+  //       const processInstance = response.data?.process_instance;
+  //       let data = {
+  //         processCode: '',
+  //         deptId: '14169890',
+  //         tasks: [],
+  //         // userId: '16569001414345099',
+  //         // deptId: currentUser.DingDepId || getCurrentUser()?.DingDepId,
+  //         userId: currentUser.DingUserId || getCurrentUser()?.DingUserId,
+  //         formComponentValues: [],
+  //         activityId: '',
+  //         cc_userids: [],
+  //         status: 'undefined',
+  //         name: curNode?.label || '未知节点',
+  //       };
+  //       if (processInstance?.tasks && processInstance.tasks?.length > 0) {
+  //         // let item = flowDetail.nodes.find(item => item.Id == version.template_node_id);
+  //         // if (!item) return data;
+  //         const { tasks, form_component_values, cc_userids } = processInstance;
+  //         data.processCode = curNode.process_code;
+  //         data.activityId = tasks[tasks.length - 1]?.activity_id;
+  //         data.tasks = tasks || [];
+  //         data.cc_userids = cc_userids;
+  //         data.formComponentValues = form_component_values?.filter(curNode => curNode.name);
+  //       }
+  //       dataList.push(data);
+  //     }
+  //   }
+  //   console.log(dataList);
+  //   setStepsData(dataList);
+  // };
+
+  const handleChangeClick = item => {
+    let file = isOut ? 'newList' : 'detail';
+    let type = item.flow_id ? '/queryAuditRecord' : '/queryAuditExcel';
+    console.log(`${file}${type}`, item);
+    dispatch({
+      type: `${file}${type}`,
+      payload: {
+        excel_id: item.id,
+        pageSize: 100,
+      },
+      callback: res => {
+        updateSteps(res, item.template_node_id);
+      },
+    });
+  };
+
+  const columns = useMemo(() => {
+    return [
+      {
+        title: '名称',
+        // width: '33%',
+        render: item => (
+          <span style={{ color: item.audit_status != 0 ? '#9b9b9b' : '' }}>
+            {item.id == version.id && !item.isParent && (
+              <CheckOutlined style={{ marginRight: 10 }} />
+            )}
+            {item.version_no && !item.isParent
+              ? `${item.version_name}.${item.version_no}`
+              : item.version_name}
+          </span>
+        ),
+      },
+      {
+        title: '状态',
+        width: '30%',
+        render: item => {
+          if (!item.flow_id && item.isParent) return;
+          let style = { color: getColor(item) };
+          let txt = '';
+          switch (item.audit_status) {
+            case 0:
+              txt = '未提交';
+              break;
+            case 1:
+              txt = '待审批';
+              break;
+            case 2:
+              txt = '已拒绝';
+              break;
+            case 3:
+              txt = '已通过';
+              break;
+            case 4:
+              txt = '已提交';
+              break;
+          }
+          if (item.status == 1) txt = '已失效';
+          return item.audit_status != 0 ? (
+            <Button onClick={() => handleChangeClick(item)}>{txt}</Button>
+          ) : (
+            <span style={style}>{txt}</span>
+          );
+        },
+      },
+      {
+        title: '操作',
+        width: '20%',
+        render: item =>
+          (item.flow_id || !item.isParent) &&
+          item.id != version.id && (
+            <a
+              onClick={() => {
+                console.log(item);
+                onChangeVersion(item);
+                onClose();
+              }}
+            >
+              加载
+            </a>
+          ),
+      },
+    ];
+  }, [version]);
+
+  const onChange = () => {
+    updateSteps([]);
   };
 
   return (
-    <Modal
-      confirmLoading={loading}
-      destroyOnClose
-      title="流程图"
-      visible={visible}
-      onCancel={onClose}
-      footer={false}
-      width="98%"
-    >
-      <Row gutter={8}>
-        <Col span={18}>
-          <Flow meta={{ type: 'view' }} flowDetail={graphData} onSelectNode={handleSelectNode} />
-        </Col>
-        <Col span={6}>
-          <List
-            size="small"
-            header={<div>清单列表</div>}
-            bordered
-            dataSource={data}
-            loading={nodeLoading}
-            renderItem={item => {
-              const isCurrent = item.id == version.id;
-              return (
-                <List.Item
-                  actions={
-                    isCurrent
-                      ? null
-                      : [
-                          <a
-                            onClick={() => {
-                              onChangeVersion(item);
-                              onClose();
-                            }}
-                          >
-                            切换
-                          </a>,
-                        ]
-                  }
+    <>
+      <Modal
+        confirmLoading={loading}
+        destroyOnClose
+        title="流程图"
+        visible={visible}
+        onCancel={onClose}
+        footer={false}
+        width="98%"
+      >
+        <Row gutter={8}>
+          <Col span={16}>
+            <Flow meta={{ type: 'view' }} flowDetail={graphData} onSelectNode={handleSelectNode} />
+          </Col>
+          <Col span={8}>
+            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
+              <div style={{ fontSize: '16px' }}>清单列表</div>
+              {isOut && (
+                <Button
+                  type="primary"
+                  style={{ float: 'right', marginBottom: '10px' }}
+                  onClick={() => setVersionVisible(true)}
                 >
-                  <div style={{ color: getColor(item) }}>
-                    {isCurrent && <CheckOutlined style={{marginRight: 10}}/>}
-                    {item.version_name}
-                  </div>
-                </List.Item>
-              );
-            }}
-          />
-        </Col>
-      </Row>
-    </Modal>
+                  新建清单
+                </Button>
+              )}
+            </div>
+
+            <div style={{ width: '100%' }}>
+              <Table
+                style={{ maxHeight: '90%' }}
+                columns={columns}
+                dataSource={data}
+                loading={nodeLoading}
+                bordered={false}
+                pagination={{ pageSize: 8, onChange }}
+                scroll={{ y: 65 * pageSize }}
+              />
+            </div>
+            {stepsData.map((item, idx) => (
+              <div key={`${item.name}_${idx}`} style={{ marginBottom: '20px' }}>
+                <div style={{ marginBottom: '4px' }}>{item.name}</div>
+                <Steps size="small" current={item.current} status={item.status}>
+                  {item.list.map(node => (
+                    <Step
+                      key={`${node.id}_${node.node}`}
+                      title={node.node}
+                      description={`审批人:${node.auditor || '-'}`}
+                    />
+                  ))}
+                </Steps>
+              </div>
+            ))}
+          </Col>
+        </Row>
+      </Modal>
+      <VersionModal
+        visible={versionVisible}
+        onClose={() => setVersionVisible(false)}
+        onOk={values => {
+          onCommit?.(values);
+          setVersionVisible(false);
+        }}
+      />
+    </>
   );
 }
 
@@ -130,6 +350,8 @@ const getColor = item => {
   return color;
 };
 
-export default connect(({ xflow, detail }) => ({
-  flowDetail: xflow.flowDetail,
+export default connect(({ loading, user }) => ({
+  loading,
+  currentUser: user.currentUser,
 }))(FlowModal);
+// export default FlowModal;

+ 267 - 62
src/pages/PurchaseAdmin/PurchaseList/Detail/Index.js

@@ -20,7 +20,11 @@ import CommitAuditModal from './CommitAuditModal';
 import CommentContent from '@/components/CommentContent';
 import MergeModal from './MergeModal';
 import { GetTokenFromUrl, getToken } from '@/utils/utils';
-import { queryDetail } from '@/services/boom';
+import { queryDetail, queryDingInstanceExecute } from '@/services/boom';
+import HistoryDrawer from './HistoryDrawer';
+import AuditFlow from './AuditFlow';
+import { getCurrentUser } from '@/utils/authority';
+import { async } from '@antv/x6/es/registry/marker/async';
 const LocalData = localStorage.luckysheet;
 
 const { Option } = Select;
@@ -38,8 +42,11 @@ function Detail(props) {
     versionList,
     auditList,
     flowDetail,
+    versionTree,
     match: { params },
+    instanceDetail,
   } = props;
+  const [versionTreeVisible, setVersionTreeVisible] = useState(false);
   const [commentVisible, setCommentVisible] = useState(false);
   const [mergeVisible, setMergeVisible] = useState(false);
   const [compareVisible, setCompareVisible] = useState(false);
@@ -65,6 +72,7 @@ function Detail(props) {
   const sheetRef2 = useRef();
   const sheetRef3 = useRef();
   const fileRef = useRef();
+  const userRef = useRef();
   const statusRef = useRef({
     edit: false,
     compare: false,
@@ -74,6 +82,33 @@ function Detail(props) {
   const projectId = parseInt(params.projectId);
   const templateId = parseInt(params.templateId);
 
+  const auditDetail = useMemo(() => {
+    let data = {
+      processCode: '',
+      deptId: '14169890',
+      tasks: [],
+      // userId: '16569001414345099',
+      // deptId: currentUser.DingDepId || getCurrentUser()?.DingDepId,
+      userId: currentUser.DingUserId || getCurrentUser()?.DingUserId,
+      formComponentValues: [],
+      activityId: '',
+      cc_userids: [],
+      status: version.audit_status,
+    };
+    if (version?.flow_id && instanceDetail?.tasks && instanceDetail.tasks?.length > 0) {
+      let item = flowDetail.nodes.find(item => item.Id == version.template_node_id);
+      if (!item) return data;
+      const { tasks, form_component_values, cc_userids } = instanceDetail;
+      data.processCode = item.process_code;
+      data.activityId = tasks[tasks.length - 1]?.activity_id;
+      data.tasks = tasks || [];
+      data.cc_userids = cc_userids;
+      data.formComponentValues = form_component_values?.filter(item => item.name);
+      if (version.status == 1) data.status = -1;
+    }
+    return data;
+  }, [instanceDetail, version]);
+
   const flow = useMemo(() => {
     let data = {
       active: 0,
@@ -94,17 +129,17 @@ function Detail(props) {
       item.currentNode = item.list.FlowNodes[item.current];
       data = item;
     }
-
     return data;
   }, [auditList, version]);
   const active_audit = flow.active_audit;
-
+  console.log(flow);
   const isAuditor = useMemo(() => {
     const getUserRole = () => {
-      let roleID = flow.currentNode?.AuditRoleInfo?.ID;
-      let item = currentUser.Permissions.find(role => role.RoleId == roleID);
-      if (item) return true;
-      return false;
+      return flow.currentNode.auditor == currentUser.ID;
+      // let roleID = flow.currentNode?.AuditRoleInfo?.ID;
+      // let item = currentUser.roleList.find(role => role.ID == roleID);
+      // if (item) return true;
+      // return false;
     };
     return active_audit == 1 && getUserRole();
   }, [active_audit, flow, currentUser]);
@@ -224,9 +259,7 @@ function Detail(props) {
         item => item.audit_status != 4 && item.template_node_id == currentNode.Id
       );
       if (serviceVersion) {
-        message.error(
-          `新建清单失败!业务节点【${currentNode.label}】为单清单节点。已存在清单【${serviceVersion.version_name}】。`
-        );
+        message.error(`新建清单失败!业务节点【${currentNode.label}】只能有一个清单!`);
         return;
       }
     }
@@ -248,6 +281,7 @@ function Detail(props) {
       new_version: '0',
       audit_status: 0,
       data: JSON.stringify(sheets),
+      base_id: version.id,
     };
     dispatch({
       type: 'detail/commitSheet',
@@ -288,11 +322,11 @@ function Detail(props) {
   };
 
   const onAudit = ({ audit_comment }) => {
-    var flowNode = flow.currentNode;
+    const flowNode = flow.currentNode;
     dispatch({
       type: 'detail/approve',
       payload: {
-        id: version.id,
+        id: flow.active_id,
         project_id: projectId,
         audit_status: 2,
         flow_id: flowNode.flow_id,
@@ -318,27 +352,6 @@ function Detail(props) {
     });
   };
 
-  const onSubmitAudit = () => {
-    Modal.confirm({
-      title: '提示',
-      content: '是否确认提交审批。',
-      okText: '确定',
-      cancelText: '取消',
-      onOk() {
-        var flowNode = flow.currentNode;
-        dispatch({
-          type: 'detail/submitAudit',
-          payload: {
-            id: version.id,
-            flow_id: flowNode.flow_id,
-            node_id: flowNode.id,
-            projectId,
-          },
-        });
-      },
-    });
-  };
-
   const onApprove = flag => {
     if (!flag) {
       setAuditVisible(true);
@@ -348,8 +361,31 @@ function Detail(props) {
     let isSingle = false;
     let serviceNode;
     const flowNode = flow.currentNode;
+    const getLastTemplateNodeId = data => {
+      let result;
+      const getFun = item => {
+        if (item.flow_path?.length > 0) {
+          getFun(item.flow_path[0]);
+        } else {
+          result = item.template_node_id;
+        }
+      };
+      if (!data) return version.template_node_id;
+      getFun(data[0]);
+      return result;
+    };
+    let lastTemplateNodeId = version.template_node_id;
+    if (version.flow_path) {
+      //如果多节点审批  获取当前是否审批流程的最后一个审批节点
+      let flowPathList = JSON.parse(version.flow_path);
+      lastTemplateNodeId = getLastTemplateNodeId(flowPathList);
+    }
+
     // 判断是否为最后一个审批节点
-    if (flow.current == flow.list.FlowNodes.length - 1) {
+    if (
+      lastTemplateNodeId == version.template_node_id &&
+      flow.current == flow.list.FlowNodes.length - 1
+    ) {
       serviceNode = flowDetail.nodes.find?.(item => item.Id == version.next_template_node_id);
       if (!serviceNode.muti_version) {
         //audit_status=4 表示为清单推进后留存的副本.不计入多清单计算
@@ -383,14 +419,25 @@ function Detail(props) {
                 id: templateId,
               },
             });
+
+            // 更新审批流
+            dispatch({
+              type: 'detail/queryAuditList',
+              payload: {
+                template_id: version.template_id,
+                template_node_id: version.template_node_id,
+                flow_id: version.flow_id,
+                version_id: version.version_id,
+              },
+            });
             if (flow.current == flow.list.FlowNodes.length - 1) {
-              // 最后一个审核节点通过后  需要更新version id
-              localStorage.excelId = newVersion.id;
-              setVersion({
-                ...version,
-                flow_id: newVersion.flow_id,
-                id: newVersion.id,
-              });
+              // 最后一个审核节点通过后  需要更新version id 不更不更,留在原地
+              // localStorage.excelId = newVersion.id;
+              // setVersion({
+              //   ...version,
+              //   flow_id: newVersion.flow_id,
+              //   id: newVersion.id,
+              // });
             }
           },
         });
@@ -398,6 +445,95 @@ function Detail(props) {
     });
   };
 
+  const onApprove1 = (flag, taskId) => {
+    const callback = () => {
+      // 更新flow流程图
+      dispatch({
+        type: 'xflow/queryBoomFlowDetail',
+        payload: {
+          id: templateId,
+        },
+      });
+
+      // 更新审批流
+      dispatch({
+        type: 'detail/queryDingInstanceDetail',
+        payload: {
+          process_instance_id: version.ding_instance_id, //创建表单成功返回的id
+        },
+      });
+    };
+
+    const request = async () => {
+      let param = {
+        request: {
+          process_instance_id: version.ding_instance_id,
+          result: flag ? 'agree' : 'refuse',
+          actioner_userid: currentUser.DingUserId || getCurrentUser()?.DingUserId,
+          task_id: taskId,
+        },
+      };
+      let res = await queryDingInstanceExecute(param);
+      if (res?.data?.result) {
+        callback();
+      }
+    };
+
+    let tipText = '是否通过审批。';
+    if (!flag) {
+      tipText = '是否拒绝审批。';
+      // request();
+      // return;
+    }
+
+    let isSingle = false;
+    let serviceNode;
+    const flowNode = flow.currentNode;
+    const getLastTemplateNodeId = data => {
+      let result;
+      const getFun = item => {
+        if (item.flow_path?.length > 0) {
+          getFun(item.flow_path[0]);
+        } else {
+          result = item.template_node_id;
+        }
+      };
+      if (!data) return version.template_node_id;
+      getFun(data[0]);
+      return result;
+    };
+    let lastTemplateNodeId = version.template_node_id;
+    if (version.flow_path) {
+      //如果多节点审批  获取当前是否审批流程的最后一个审批节点
+      let flowPathList = JSON.parse(version.flow_path);
+      lastTemplateNodeId = getLastTemplateNodeId(flowPathList);
+    }
+
+    // 判断是否为最后一个审批节点
+    if (
+      lastTemplateNodeId == version.template_node_id &&
+      flow.current == flow.list.FlowNodes.length - 1
+    ) {
+      serviceNode = flowDetail.nodes.find?.(item => item.Id == version.next_template_node_id);
+      if (!serviceNode.muti_version) {
+        //audit_status=4 表示为清单推进后留存的副本.不计入多清单计算
+        isSingle = versionList.find(
+          item => item.audit_status != 4 && item.template_node_id == serviceNode.Id
+        );
+        if (isSingle) tipText = `节点【${serviceNode.label}】只能拥有一个清单,是否覆盖?`;
+      }
+    }
+    Modal.confirm({
+      title: '提示',
+      content: tipText,
+      okText: '确定',
+      cancelText: '取消',
+      onOk: () => {
+        request();
+      },
+    });
+  };
+
   const onMerge = () => {
     const [sheet1, sheet2] = compareList;
     Modal.confirm({
@@ -442,6 +578,7 @@ function Detail(props) {
     //   sheetRef3.current.mergeExcl(sheet.data);
     // }, 400);
   };
+
   const handleClickFile = () => {
     fileRef.current.click();
   };
@@ -504,10 +641,6 @@ function Detail(props) {
         setCommitVisible(true);
         setCommentVisible(false);
         break;
-      case 'approval':
-        // 申请审批
-        onSubmitAudit();
-        break;
       case 'attachment':
         // 附件
         setFileVisible(true);
@@ -544,7 +677,22 @@ function Detail(props) {
     ];
     // version.audit_status:4 为副本。不可操作
     if (version.audit_status != 4) {
-      menuList.push(<Menu.Item key="commitAudit">提交流转</Menu.Item>);
+      //判断权限配置,如果配置了,就指定权限的人可提交,没配置就全部人都可提交
+      const getIsSubmit = () => {
+        const nodeId = version.template_node_id;
+        if (!flowDetail?.nodes || !nodeId) return;
+        const node = flowDetail.nodes.find(item => item.Id == nodeId);
+        if (!node || node.name == 'custom-circle') return;
+        if (node?.role_list) {
+          return node.role_list
+            .split(',')
+            .some(id => currentUser.roleList?.find(role => role.ID == id));
+        }
+        return true;
+      };
+      // console.log('是否有权限提交流转   ', getIsSubmit());
+      if (getIsSubmit() && version.audit_status != 3)
+        menuList.push(<Menu.Item key="commitAudit">提交流转</Menu.Item>);
 
       if (!isAuditor && canEdit() && !version.flow_id) {
         // menuList.push(<Menu.Item key="edit">编辑</Menu.Item>);
@@ -574,7 +722,9 @@ function Detail(props) {
     let item = '';
     switch (active_audit) {
       case 0:
-        return;
+        if (!flow.list || flow.list.FlowNodes?.length == 0) return;
+        item = <Alert message="审批拒绝" type="error" />;
+        break;
       case 1:
         item = <Alert message="等待审核中" type="info" />;
         break;
@@ -714,12 +864,20 @@ function Detail(props) {
     if (typeof id == 'object') {
       version = id;
       localStorage.excelId = version.id;
+      localStorage.excelItem = JSON.stringify(version);
     } else {
       version = versionList.find(item => item.id == id);
       if (!version) return;
       localStorage.excelId = id;
     }
     setVersion(version);
+    //请求历史版本
+    dispatch({
+      type: 'detail/queryVersionsTree',
+      payload: {
+        excel_id: version.id || localStorage.excelId,
+      },
+    });
 
     // 判断是否审批节点
     if (version.flow_id) {
@@ -742,12 +900,12 @@ function Detail(props) {
       callback: newVersion => {
         setCommitAuditVisible(false);
         // 更新version
-        localStorage.excelId = newVersion.id;
-        setVersion({
-          ...version,
-          flow_id: newVersion.flow_id,
-          id: newVersion.id,
-        });
+        // localStorage.excelId = newVersion.id;
+        // changeVersion({
+        //   ...version,
+        //   ...newVersion,
+        //   version_id: version.id
+        // });
         // 更新flow流程图
         dispatch({
           type: 'xflow/queryBoomFlowDetail',
@@ -759,15 +917,26 @@ function Detail(props) {
     });
   };
 
-  const getUser = user => {
-    setUser(user);
+  const getUser = newUser => {
+    try {
+      if (JSON.stringify(newUser) != JSON.stringify(userRef.current)) {
+        userRef.current = newUser;
+        setUser(newUser);
+      }
+    } catch (error) {}
   };
 
   const renderNode = () => {
     const nodeId = version.template_node_id;
     if (!flowDetail?.nodes || !nodeId) return;
     const node = flowDetail.nodes.find(item => item.Id == nodeId);
-    return `当前清单:${version.version_name || '-'};    当前节点:${node?.label || '-'}`;
+    // return `当前清单:${version.version_name || '-'};    当前节点:${node?.label || '-'}`;
+    return (
+      <span className={styles.curTitle}>
+        当前清单: <span>{version.version_name || '-'}</span>当前节点:{' '}
+        <span>{node?.label || '-'}</span>
+      </span>
+    );
   };
 
   const handleSubmitCell = (value, callback) => {
@@ -798,6 +967,15 @@ function Detail(props) {
     dispatch({
       type: 'user/fetch',
     });
+    dispatch({
+      type: 'user/fetchDepV2',
+    });
+    dispatch({
+      type: 'detail/queryListParentByUser',
+      payload: {
+        userid: currentUser.DingUserId || getCurrentUser()?.DingUserId,
+      },
+    });
   }, []);
 
   useEffect(() => {
@@ -818,7 +996,9 @@ function Detail(props) {
   useEffect(() => {
     if (versionList.length == 0) return;
     if (!version.id) {
-      const excelId = localStorage.excelId;
+      const excelId = localStorage.excelItem
+        ? JSON.parse(localStorage.excelItem)
+        : localStorage.excelId;
       changeVersion(excelId);
     } else {
       changeVersion(version.id);
@@ -839,17 +1019,27 @@ function Detail(props) {
               新建清单
             </Button>
           )}
-          <span style={{ marginLeft: 20 }}>{renderNode()}</span>
+          {renderNode()}
         </div>
         <div className={styles.btns}>
+          <Button
+            type="primary"
+            style={{ marginRight: 20 }}
+            onClick={() => setVersionTreeVisible(true)}
+          >
+            历史版本
+          </Button>
           <Avatar.Group style={{ marginRight: 20 }}>
-            {user.map(item => (
-              <Avatar style={{ backgroundColor: '#008dff' }} size="large">
+            {user.map((item, id) => (
+              <Avatar
+                key={`${id}-${item.name}`}
+                style={{ backgroundColor: '#008dff' }}
+                size="large"
+              >
                 {item.Name}
               </Avatar>
             ))}
           </Avatar.Group>
-
           {renderBtns()}
         </div>
         <input
@@ -860,8 +1050,11 @@ function Detail(props) {
         />
       </div>
       <TimeNode flow={flow} isAuditor={isAuditor} onApprove={onApprove}></TimeNode>
+      {/* {version.flow_id ? (
+        <AuditFlow {...auditDetail} canShowAudit={true} onApprove={onApprove} />
+      ) : null} */}
 
-      {renderAlert()}
+      {/* {renderAlert()} */}
       {/* 判断是否为比对模式 */}
       {compareList.length == 2 ? (
         <>
@@ -887,6 +1080,14 @@ function Detail(props) {
         </div>
       )}
 
+      <HistoryDrawer
+        versionTree={versionTree}
+        version={version}
+        visible={versionTreeVisible}
+        onChangeVersion={version => changeVersion(version)}
+        onClose={() => setVersionTreeVisible(false)}
+      />
+
       <CommentContent
         title="单元格沟通记录"
         comment={comment}
@@ -918,6 +1119,7 @@ function Detail(props) {
         onOk={downloadExcel}
       />
       <FlowModal
+        flowDetail={flowDetail}
         visible={flowVisible}
         onClose={() => setFlowVisible(false)}
         version={version}
@@ -950,6 +1152,7 @@ function Detail(props) {
         version={version}
         onClose={() => setCommitAuditVisible(false)}
         onOk={onSubmitNextNode}
+        luckysheet={sheetRef}
       />
     </Spin>
   );
@@ -961,8 +1164,10 @@ export default connect(({ detail, user, xflow, loading }) => ({
   fileList: detail.fileList,
   history: detail.history,
   comment: detail.comment,
+  instanceDetail: detail.dingInstanceDetail,
   currentUser: user.currentUser,
   roleList: detail.roleList,
   versionList: detail.versionList,
+  versionTree: detail.versionTree,
   loading,
 }))(Detail);

+ 10 - 0
src/pages/PurchaseAdmin/PurchaseList/Detail/Index.less

@@ -47,3 +47,13 @@
 .historyList {
   padding: 0 10px;
 }
+.curTitle  {
+  margin-left: 20px;
+ span {
+  color: #1890FF;
+  margin-right: 10px;
+ }
+}
+.topF{
+  margin: 20px;
+}

+ 24 - 15
src/pages/PurchaseAdmin/PurchaseList/Detail/LuckySheet.js

@@ -1,6 +1,6 @@
 import React from 'react';
 import { message } from 'antd';
-import exportExcel from '@/utils/exportExcl';
+import exportExcel, { getExcelBolob } from '@/utils/exportExcl';
 import LuckyExcel from 'luckyexcel';
 import { getToken } from '@/utils/utils';
 
@@ -116,16 +116,17 @@ class LuckySheet extends React.Component {
         //   }, 300);
         // },
       };
-      if(version.flow_id) {
+      console.log(version);
+      if (version.flow_id) {
         option.authority = {
           sheet: true,
-          hintText: '当前处于审批节点,禁止编辑!'
-        }
-      } else if(version.audit_status == 4) {
+          hintText: '当前处于审批节点,禁止编辑!',
+        };
+      } else if (version.audit_status != 0 || version.status == 1) {
         option.authority = {
           sheet: true,
-          hintText: '当前清单为历史清单,不可编辑!'
-        }
+          hintText: '当前清单不可编辑!',
+        };
       }
     } else if (data && data.length > 0) {
       option.data = JSON.parse(JSON.stringify(data));
@@ -438,7 +439,7 @@ class LuckySheet extends React.Component {
   //   });
   // }
 
-  downloadExcel(checkValue) {
+  getExcelData(checkValue = null) {
     let resultList = [];
     console.log(this.luckysheet.getAllSheets());
 
@@ -446,24 +447,21 @@ class LuckySheet extends React.Component {
     currentData.forEach(sheet => {
       let data = sheet.data;
       let celldata = sheet.celldata;
-      //获取当前导出列的数组
       let colList = [];
       data[0]?.forEach((rowOneItem, colIdx) => {
-        if (rowOneItem && checkValue.indexOf(rowOneItem.v) !== -1) {
-          colList.indexOf(colIdx) == -1 ? colList.push(colIdx) : true;
+        if (rowOneItem) {
+          if (!checkValue || checkValue.indexOf(rowOneItem.v) !== -1) {
+            colList.indexOf(colIdx) == -1 ? colList.push(colIdx) : true;
+          }
         }
       });
 
-      //处理data
       const newData = [];
       data.forEach(item => {
         if (item !== null) {
           let arr = item.filter((cur, idx) => {
             return item && colList.includes(idx);
           });
-          // let len = item.length - arr.length
-          // let nullList = new Array(len).fill(null);
-          // let rowList =  arr.concat(nullList);
           newData.push(arr);
         }
       });
@@ -484,6 +482,17 @@ class LuckySheet extends React.Component {
       });
       sheet.celldata = newCellData;
     });
+
+    return currentData;
+  }
+
+  getExcelBolb() {
+    let currentData = this.getExcelData();
+    return getExcelBolob(currentData);
+  }
+
+  downloadExcel(checkValue) {
+    let currentData = this.getExcelData(checkValue);
     exportExcel(currentData, '下载');
   }
 

+ 8 - 4
src/pages/PurchaseAdmin/PurchaseList/Detail/TimeNode.js

@@ -14,20 +14,24 @@ function TimeNode(props) {
     onApprove,
   } = props;
 
-  if (active !== 0) {
+  if (!list || list.FlowNodes?.length != 0) {
     return (
       <div className={styles.top}>
-        <Steps current={current}>
+        <Steps current={current} status={active == 0 ? 'error' : 'process'}>
           {list.FlowNodes.map(item => (
             <Step
               key={item.id}
               title={item.node}
-              description={`审批角色:${item?.AuditRoleInfo.Name || '-'}`}
+              description={
+                item?.AuditRoleInfo
+                  ? `审批人:${item?.AuditRoleInfo.Name || '-'}`
+                  : `审批人:${item?.AuditorUser.CName || '-'}`
+              }
             />
           ))}
         </Steps>
         <div className={styles.btns} style={{ marginLeft: 80 }}>
-          {isAuditor && (
+          {isAuditor && active != 0 && (
             <>
               <Button type="primary" onClick={() => onApprove(true)}>
                 审批通过

+ 76 - 2
src/pages/PurchaseAdmin/PurchaseList/Detail/models/detail.js

@@ -24,10 +24,17 @@ import {
   queryHistoryList,
   queryHistoryDetail,
   submitNextNode,
+  advanceSubmitNextNode,
   addBomComment,
   queryBomComment,
   approve,
   queryAuthority,
+  queryVersionsTree,
+  queryAuditExcel,
+  queryAuditRecord,
+  queryDingSchema,
+  queryDingInstanceDetail,
+  queryListParentByUser,
 } from '@/services/boom';
 import { queryRole } from '@/services/SysAdmin';
 import { setCurrentUser } from '@/utils/authority';
@@ -68,6 +75,9 @@ export default {
     fileList: [],
     roleList: [],
     authority: [],
+    versionTree: [],
+    auditExcel: [],
+    dingInstanceD: [],
   },
 
   effects: {
@@ -162,8 +172,8 @@ export default {
             // template_id: payload.template_id,
             // template_node_id: payload.template_node_id,
           },
-          callback: (versionList) => {
-            let newVersion = versionList.find(item => item.id == response.data.id)
+          callback: versionList => {
+            let newVersion = versionList.find(item => item.id == response.data.id);
             callback && callback(newVersion);
           },
         });
@@ -214,6 +224,25 @@ export default {
         });
       }
     },
+    // 实时计算审批人
+    *advanceSubmitNextNode({ payload, callback }, { call, put }) {
+      let response = yield call(advanceSubmitNextNode, payload);
+      if (response) {
+        console.log(response);
+        callback?.(response.data);
+
+        // message.success('提交成功');
+        // yield put({
+        //   type: 'queryVersionsList',
+        //   payload: {
+        //     project_id: payload.project_id,
+        //   },
+        //   callback: () => {
+        //     callback(response.data);
+        //   },
+        // });
+      }
+    },
     *queryDetail({ payload, callback }, { call, put }) {
       const sheet = yield call(queryDetail, payload);
       if (sheet) {
@@ -487,6 +516,51 @@ export default {
         });
       }
     },
+    *queryVersionsTree({ payload }, { call, put }) {
+      const response = yield call(queryVersionsTree, payload);
+      if (response) {
+        yield put({
+          type: 'save',
+          payload: { versionTree: response.data },
+        });
+      }
+    },
+    *queryAuditExcel({ payload, callback }, { call, put }) {
+      const response = yield call(queryAuditExcel, payload);
+      if (response) {
+        callback && callback(response.data?.all);
+      }
+    },
+    *queryAuditRecord({ payload, callback }, { call, put }) {
+      const response = yield call(queryAuditRecord, payload);
+      if (response) {
+        callback && callback(response.data?.all);
+      }
+    },
+    *queryDingSchema({ payload, callback }, { call, put }) {
+      const response = yield call(queryDingSchema, payload);
+      if (response) {
+        callback && callback(response.data?.all);
+      }
+    },
+    *queryDingInstanceDetail({ payload, callback }, { call, put }) {
+      const response = yield call(queryDingInstanceDetail, payload);
+      if (response) {
+        yield put({
+          type: 'save',
+          payload: { dingInstanceDetail: response.data?.process_instance },
+        });
+      }
+    },
+    *queryListParentByUser({ payload, callback }, { call, put }) {
+      const response = yield call(queryListParentByUser, payload);
+      if (response) {
+        yield put({
+          type: 'save',
+          payload: { parentDepList: response.data?.result },
+        });
+      }
+    },
   },
 
   reducers: {

+ 85 - 213
src/pages/PurchaseAdmin/PurchaseList/Flow/Audit.js

@@ -1,239 +1,111 @@
-import React, { useState, useEffect } from 'react';
-import { Form, Select, Button, Table, Input, Checkbox, Divider } from 'antd';
+import React, { useState, useEffect, useRef, useMemo } from 'react';
+import { Form, Select, Button, Table, Input, Checkbox, Divider, Tabs } from 'antd';
 import { connect } from 'dva';
 import AuditNodeModal from './AuditNodeModal';
 import AuditModal from './AuditModal';
 import styles from './Audit.less';
 import router from 'umi/router';
-
+import Flow, { FLOW_TYPE } from '@/components/Flow';
+import AuditForm from '@/components/AuditForm';
+import { async } from '@antv/x6/lib/registry/marker/async';
 const { Option } = Select;
+const { TabPane } = Tabs;
+
+const FLOWID = 2;
 
 function Audit(props) {
-  const { roleList, currentItem, dispatch } = props;
-  const [visible, setVisible] = useState({
-    audit: false,
-    auditNode: false,
-  });
-  const [current, setCurrent] = useState({ ...currentItem });
-  const [currentNode, setCurrentNode] = useState({});
-  const columns = [
-    {
-      title: '节点名',
-      width: "20%",
-      dataIndex: 'node',
-    },
-    // {
-    //   title: '审批级别',
-    //   dataIndex: 'seq',
-    //   width: "20%",
-    // },
-    {
-      title: '审批角色',
-      dataIndex: 'audit_role',
-      width: "20%",
-      render: audit_role => roleList.find(item => item.ID == audit_role)?.Name,
-    },
-    // {
-    //   title: '审批关系',
-    //   width: "20%",
-    //   dataIndex: 'seq_relate',
-    //   render: relate => {
-    //     switch (relate) {
-    //       case 0:
-    //         return '无';
-    //       case 1:
-    //         return '或';
-    //       case 2:
-    //         return '并';
-    //     }
-    //   },
-    // },
-    {
-      title: '操作',
-      width: "20%",
-      render: item => (
-        <>
-          <a
-            onClick={() => {
-              setCurrentNode(item);
-              changeVisible('auditNode', true);
-            }}
-          >
-            编辑
-          </a>
-          <Divider type="vertical" />
-          <a
-            onClick={() => {
-              handleDelete(item);
-            }}
-          >
-            删除
-          </a>
-        </>
-      ),
-    },
-  ];
-  // const handleAuditOk = values => {
-  //   console.log(values);
-  //   dispatch({
-  //     type: 'flow/addAudit',
-  //     payload: values,
-  //     callback: () => {
-  //       changeVisible('audit', false);
-  //     },
-  //   });
-  // };
-  const handleAuditNodeOk = values => {
-    console.log(values);
-    let FlowNodes = current.FlowNodes;
+  const {
+    roleList,
+    currentItem,
+    dispatch,
+    formItems,
+    formData,
+    flowDetail,
+    simpleFlowDteail,
+  } = props;
+  const ref = useRef();
 
-    changeVisible('auditNode', false);
-    if (currentNode.id) {
-      let index = FlowNodes.findIndex(item => item.id == currentNode.id);
-      let newNodes = [...FlowNodes];
-      newNodes[index] = values;
-      // 将同审批级别的审批关系设置为相同
-      newNodes.forEach(item => {
-        if (item.seq == values.seq) {
-          item.seq_relate = values.seq_relate;
-        }
-      });
-      // 排序
-      newNodes.sort((a, b) => a.seq - b.seq);
-      setCurrent({
-        ...current,
-        FlowNodes: newNodes,
-      });
-    } else {
-      setCurrent({
-        ...current,
-        FlowNodes: [...FlowNodes, values],
-      });
-    }
-  };
-  const handleDelete = item => {
-    let newNodes = [...current.FlowNodes];
-    let index = newNodes.indexOf(item);
-    newNodes.splice(index, 1);
-    setCurrent({
-      ...current,
-      FlowNodes: newNodes,
+  const curItem = useMemo(() => {
+    let item = localStorage.getItem('currentAudit');
+    return JSON.stringify(currentItem) == '{}' ? JSON.parse(item) : currentItem;
+  }, [currentItem, localStorage.getItem('currentAudit')]);
+
+  useEffect(() => {
+    dispatch({
+      type: 'flow/queryProcessFlows',
+      payload: { ids: Number(curItem.id) },
+    });
+    dispatch({
+      type: 'user/getRoleList',
+    });
+    dispatch({
+      type: 'user/fetch',
     });
-  };
-  // const onCancel = () => {
-  //   form.resetFields();
-  //   setCurrent({});
-  // };
-  const onSave = () => {
-    const nodes = current.FlowNodes.map((item,index) => ({
-      flow_id: current.id,
-      node: item.node,
-      desc: item.desc,
-      audit_role: item.audit_role,
-      seq: index + 1,
-      // seq_relate: item.seq_relate,
-    }));
 
     dispatch({
-      type: 'flow/addAuditNode',
+      type: 'user/fetchDepV2',
+    });
+  }, []);
+  const onChange = values => {
+    dispatch({
+      type: 'xflow/save',
       payload: {
-        flowId: current.id,
-        nodes: nodes,
+        formData: values,
       },
     });
   };
-  // const handleSelect = id => {
-  //   const item = list.find(item => item.list?.id == id);
-  //   console.log(item);
-  //   setCurrent({
-  //     ...item.list,
-  //   });
-  // };
-  const changeVisible = (type, visible) => {
-    setVisible({
-      ...visible,
-      [type]: visible,
-    });
-  };
-
-  // const renderTitle = () => {
-  //   return (
-  //     <div className={styles.box}>
-  //       <span>审批详情</span>
-  //       {current?.id && (
-  //         <div className={styles.btns}>
-  //           {/* <Button onClick={onCancel}>取消</Button> */}
-  //           <Button onClick={onSave} type="primary">
-  //             保存
-  //           </Button>
-  //           <Button
-  //             onClick={() => {
-  //               setCurrentNode({});
-  //               changeVisible('auditNode', true);
-  //             }}
-  //             type="primary"
-  //           >
-  //             添加节点
-  //           </Button>
-  //         </div>
-  //       )}
-  //     </div>
-  //   );
-  // };
 
-  useEffect(() => {
-    if (!current.id) {
-      router.go(-1);
-    } else {
-      dispatch({
-        type: 'user/fetch',
-      });
-      dispatch({
-        type: 'flow/queryAuditList',
-      });
-      dispatch({
-        type: 'flow/getRoleList',
-      });
+  const handleSaveClick = async () => {
+    //只修改表单不渲染xflow getGraphData方法找不到,保存接口返回的flowDetail数据
+    if (!ref.current?.getGraphData) {
+      let param = {
+        // name: curItem.name,
+        id: Number(curItem.id),
+        form_json: JSON.stringify(formItems),
+        process_json: JSON.stringify(flowDetail),
+        process_simple_json: simpleFlowDteail,
+      };
+      dispatch({ type: 'flow/saveAuditFlowInfo', payload: param });
+      return;
     }
-  }, []);
-
+    await ref.current?.getGraphData?.((data, simpleNodes) => {
+      let param = {
+        // name: curItem.name,
+        id: Number(curItem.id),
+        form_json: JSON.stringify(formItems),
+        process_json: data,
+        process_simple_json: simpleNodes,
+      };
+      dispatch({ type: 'flow/saveAuditFlowInfo', payload: param });
+    });
+  };
   return (
-    <div>
-      <div className={styles.btns}>
-        <Button onClick={() => router.go(-1)}>返回</Button>
-        {/* <Button onClick={onCancel}>取消</Button> */}
-        <Button onClick={onSave} type="primary">
-          保存
-        </Button>
-        <Button
-          onClick={() => {
-            setCurrentNode({});
-            changeVisible('auditNode', true);
-          }}
-          type="primary"
-        >
-          添加节点
-        </Button>
-      </div>
-
-      <Table rowKey="id" dataSource={current?.FlowNodes || []} columns={columns} />
-      {/* <AuditModal
-        visible={visible.audit}
-        onOk={handleAuditOk}
-        onCancel={() => changeVisible('audit', false)}
-      /> */}
-      <AuditNodeModal
-        roleList={roleList}
-        data={currentNode}
-        visible={visible.auditNode}
-        onOk={handleAuditNodeOk}
-        onCancel={() => changeVisible('auditNode', false)}
-      />
+    <div style={{ position: 'relative' }}>
+      <p>{curItem.name}</p>
+      <Tabs defaultActiveKey="1">
+        <TabPane tab="表单设计" key="1">
+          <AuditForm value={formData} onChange={values => onChange(values)} />
+        </TabPane>
+        <TabPane tab="流程控制" key="2">
+          <Flow meta={{ type: 'edit', flowId: curItem.id }} flowDetail={flowDetail} ref={ref} />
+        </TabPane>
+      </Tabs>
+      <Button
+        type="primary"
+        onClick={handleSaveClick}
+        style={{ position: 'absolute', right: 0, top: 0 }}
+      >
+        保存
+      </Button>
     </div>
   );
 }
-export default connect(({ flow, loading }) => ({
+export default connect(({ flow, loading, xflow }) => ({
   roleList: flow.roleList,
   currentItem: flow.current,
   loading: loading.models.purchaseList2,
+  formItems: xflow.formData,
+  flowDetail: flow.flowDetail,
+  formData: flow.formData,
+  simpleFlowDteail: flow.simpleFlowDteail,
 }))(Audit);

+ 8 - 11
src/pages/PurchaseAdmin/PurchaseList/Flow/AuditList.js

@@ -18,7 +18,7 @@ function Audit(props) {
   const columns = [
     {
       title: '审批流名称',
-      dataIndex: ['list','name'],
+      dataIndex: ['list', 'name'],
     },
     {
       title: '操作',
@@ -51,15 +51,16 @@ function Audit(props) {
       [type]: visible,
     });
   };
-  const setCurrentNode = (item) => {
+  const setCurrentNode = item => {
+    if (item?.list) localStorage.setItem('currentAudit', JSON.stringify(item.list));
     dispatch({
       type: 'flow/save',
       payload: {
         current: item.list,
-      }
-    })
-    router.push("/home/audit")
-  }
+      },
+    });
+    router.push('/home/audit');
+  };
 
   useEffect(() => {
     dispatch({
@@ -80,11 +81,7 @@ function Audit(props) {
         </Form>
       </div>
 
-      <Table
-        rowKey="id"
-        dataSource={list}
-        columns={columns}
-      />
+      <Table rowKey="id" dataSource={list} columns={columns} />
       <AuditModal
         visible={visible.audit}
         onOk={handleAuditOk}

+ 3 - 0
src/pages/PurchaseAdmin/PurchaseList/Flow/Flow.js

@@ -49,6 +49,9 @@ class FlowPage extends React.PureComponent {
     dispatch({
       type: 'xflow/queryAuditList',
     });
+    dispatch({
+      type: 'xflow/fetchDepV2',
+    });
 
     UnityAction.on('NODE_SAVE', nodeConfig => {
       this.onUpdate(nodeConfig);

+ 626 - 224
src/pages/PurchaseAdmin/PurchaseList/Flow/FlowDetail.json

@@ -10,10 +10,22 @@
       "height": 50,
       "ports": {
         "items": [
-          { "group": "top", "id": "293a90b5" },
-          { "group": "right", "id": "92334433" },
-          { "group": "bottom", "id": "c2ab5849" },
-          { "group": "left", "id": "6079a903" }
+          {
+            "group": "top",
+            "id": "293a90b5"
+          },
+          {
+            "group": "right",
+            "id": "92334433"
+          },
+          {
+            "group": "bottom",
+            "id": "c2ab5849"
+          },
+          {
+            "group": "left",
+            "id": "6079a903"
+          }
         ]
       },
       "isCustom": true,
@@ -31,15 +43,27 @@
       "height": 90,
       "ports": {
         "items": [
-          { "group": "top", "id": "a61170c3" },
-          { "group": "right", "id": "821f59c0" },
-          { "group": "bottom", "id": "17360bc4" },
-          { "group": "left", "id": "15d1b217" }
+          {
+            "group": "top",
+            "id": "a61170c3"
+          },
+          {
+            "group": "right",
+            "id": "821f59c0"
+          },
+          {
+            "group": "bottom",
+            "id": "17360bc4"
+          },
+          {
+            "group": "left",
+            "id": "15d1b217"
+          }
         ]
       },
       "isCustom": true,
       "parentKey": "1",
-      "x": -369,
+      "x": -290,
       "y": -170,
       "zIndex": 10
     },
@@ -52,15 +76,27 @@
       "height": 50,
       "ports": {
         "items": [
-          { "group": "top", "id": "4b4d9fa6" },
-          { "group": "right", "id": "ce88d7e2" },
-          { "group": "bottom", "id": "e69d8709" },
-          { "group": "left", "id": "c29d7b43" }
+          {
+            "group": "top",
+            "id": "4b4d9fa6"
+          },
+          {
+            "group": "right",
+            "id": "ce88d7e2"
+          },
+          {
+            "group": "bottom",
+            "id": "e69d8709"
+          },
+          {
+            "group": "left",
+            "id": "c29d7b43"
+          }
         ]
       },
       "isCustom": true,
       "parentKey": "1",
-      "x": -162,
+      "x": -138,
       "y": -150,
       "zIndex": 10
     },
@@ -73,10 +109,22 @@
       "height": 90,
       "ports": {
         "items": [
-          { "group": "top", "id": "331cd291" },
-          { "group": "right", "id": "ff6724ee" },
-          { "group": "bottom", "id": "16b4df46" },
-          { "group": "left", "id": "34c9dbc6" }
+          {
+            "group": "top",
+            "id": "331cd291"
+          },
+          {
+            "group": "right",
+            "id": "ff6724ee"
+          },
+          {
+            "group": "bottom",
+            "id": "16b4df46"
+          },
+          {
+            "group": "left",
+            "id": "34c9dbc6"
+          }
         ]
       },
       "isCustom": true,
@@ -94,10 +142,22 @@
       "height": 50,
       "ports": {
         "items": [
-          { "group": "top", "id": "f71ce55b" },
-          { "group": "right", "id": "e67dc19c" },
-          { "group": "bottom", "id": "a06aba2c" },
-          { "group": "left", "id": "b578cc26" }
+          {
+            "group": "top",
+            "id": "f71ce55b"
+          },
+          {
+            "group": "right",
+            "id": "e67dc19c"
+          },
+          {
+            "group": "bottom",
+            "id": "a06aba2c"
+          },
+          {
+            "group": "left",
+            "id": "b578cc26"
+          }
         ]
       },
       "isCustom": true,
@@ -115,10 +175,22 @@
       "height": 50,
       "ports": {
         "items": [
-          { "group": "top", "id": "0fc44196" },
-          { "group": "right", "id": "d2030f1b" },
-          { "group": "bottom", "id": "188c9b68" },
-          { "group": "left", "id": "4e9ce7ad" }
+          {
+            "group": "top",
+            "id": "0fc44196"
+          },
+          {
+            "group": "right",
+            "id": "d2030f1b"
+          },
+          {
+            "group": "bottom",
+            "id": "188c9b68"
+          },
+          {
+            "group": "left",
+            "id": "4e9ce7ad"
+          }
         ]
       },
       "isCustom": true,
@@ -136,15 +208,27 @@
       "height": 50,
       "ports": {
         "items": [
-          { "group": "top", "id": "b58731c5" },
-          { "group": "right", "id": "b3dfbc16" },
-          { "group": "bottom", "id": "89c0bc16" },
-          { "group": "left", "id": "a3adcac9" }
+          {
+            "group": "top",
+            "id": "b58731c5"
+          },
+          {
+            "group": "right",
+            "id": "b3dfbc16"
+          },
+          {
+            "group": "bottom",
+            "id": "89c0bc16"
+          },
+          {
+            "group": "left",
+            "id": "a3adcac9"
+          }
         ]
       },
       "isCustom": true,
       "parentKey": "1",
-      "x": -384,
+      "x": -305,
       "y": -304,
       "zIndex": 10
     },
@@ -157,15 +241,27 @@
       "height": 50,
       "ports": {
         "items": [
-          { "group": "top", "id": "7f8dc65b" },
-          { "group": "right", "id": "9a699e3f" },
-          { "group": "bottom", "id": "d348b56a" },
-          { "group": "left", "id": "47317157" }
+          {
+            "group": "top",
+            "id": "7f8dc65b"
+          },
+          {
+            "group": "right",
+            "id": "9a699e3f"
+          },
+          {
+            "group": "bottom",
+            "id": "d348b56a"
+          },
+          {
+            "group": "left",
+            "id": "47317157"
+          }
         ]
       },
       "isCustom": true,
       "parentKey": "1",
-      "x": -162,
+      "x": -138,
       "y": -304,
       "zIndex": 10
     },
@@ -178,15 +274,27 @@
       "height": 90,
       "ports": {
         "items": [
-          { "group": "top", "id": "cf1c4df0" },
-          { "group": "right", "id": "1eb352b0" },
-          { "group": "bottom", "id": "83b59198" },
-          { "group": "left", "id": "94f485b5" }
+          {
+            "group": "top",
+            "id": "cf1c4df0"
+          },
+          {
+            "group": "right",
+            "id": "1eb352b0"
+          },
+          {
+            "group": "bottom",
+            "id": "83b59198"
+          },
+          {
+            "group": "left",
+            "id": "94f485b5"
+          }
         ]
       },
       "isCustom": true,
       "parentKey": "1",
-      "x": 213,
+      "x": 356,
       "y": -324,
       "zIndex": 10
     },
@@ -199,15 +307,27 @@
       "height": 50,
       "ports": {
         "items": [
-          { "group": "top", "id": "3ca1320c" },
-          { "group": "right", "id": "4dee75d9" },
-          { "group": "bottom", "id": "0f72f2ba" },
-          { "group": "left", "id": "bac7962b" }
+          {
+            "group": "top",
+            "id": "3ca1320c"
+          },
+          {
+            "group": "right",
+            "id": "4dee75d9"
+          },
+          {
+            "group": "bottom",
+            "id": "0f72f2ba"
+          },
+          {
+            "group": "left",
+            "id": "bac7962b"
+          }
         ]
       },
       "isCustom": true,
       "parentKey": "1",
-      "x": 198,
+      "x": 446,
       "y": -150,
       "zIndex": 10
     },
@@ -220,15 +340,27 @@
       "height": 50,
       "ports": {
         "items": [
-          { "group": "top", "id": "04c81e99" },
-          { "group": "right", "id": "0d594eef" },
-          { "group": "bottom", "id": "17ff5fe6" },
-          { "group": "left", "id": "73307680" }
+          {
+            "group": "top",
+            "id": "04c81e99"
+          },
+          {
+            "group": "right",
+            "id": "0d594eef"
+          },
+          {
+            "group": "bottom",
+            "id": "17ff5fe6"
+          },
+          {
+            "group": "left",
+            "id": "73307680"
+          }
         ]
       },
       "isCustom": true,
       "parentKey": "1",
-      "x": 386,
+      "x": 621,
       "y": -150,
       "zIndex": 10
     },
@@ -241,15 +373,27 @@
       "height": 50,
       "ports": {
         "items": [
-          { "group": "top", "id": "2aae2a71" },
-          { "group": "right", "id": "e3a42bda" },
-          { "group": "bottom", "id": "0f06668a" },
-          { "group": "left", "id": "8e0bff55" }
+          {
+            "group": "top",
+            "id": "2aae2a71"
+          },
+          {
+            "group": "right",
+            "id": "e3a42bda"
+          },
+          {
+            "group": "bottom",
+            "id": "0f06668a"
+          },
+          {
+            "group": "left",
+            "id": "8e0bff55"
+          }
         ]
       },
       "isCustom": true,
       "parentKey": "1",
-      "x": 198,
+      "x": 446,
       "y": 26,
       "zIndex": 10
     },
@@ -257,20 +401,32 @@
       "id": "3fb8d302",
       "renderKey": "custom-circle",
       "name": "custom-circle",
-      "label": "三级审批",
+      "label": "三级审批4",
       "width": 90,
       "height": 90,
       "ports": {
         "items": [
-          { "group": "top", "id": "8ab6c3f6" },
-          { "group": "right", "id": "205f1437" },
-          { "group": "bottom", "id": "761ad2b5" },
-          { "group": "left", "id": "22d16375" }
+          {
+            "group": "top",
+            "id": "8ab6c3f6"
+          },
+          {
+            "group": "right",
+            "id": "205f1437"
+          },
+          {
+            "group": "bottom",
+            "id": "761ad2b5"
+          },
+          {
+            "group": "left",
+            "id": "22d16375"
+          }
         ]
       },
       "isCustom": true,
       "parentKey": "1",
-      "x": 42,
+      "x": 308,
       "y": -170,
       "zIndex": 10
     },
@@ -283,10 +439,22 @@
       "height": 90,
       "ports": {
         "items": [
-          { "group": "top", "id": "99f69f24" },
-          { "group": "right", "id": "0bccd839" },
-          { "group": "bottom", "id": "42c0d925" },
-          { "group": "left", "id": "58f52f2c" }
+          {
+            "group": "top",
+            "id": "99f69f24"
+          },
+          {
+            "group": "right",
+            "id": "0bccd839"
+          },
+          {
+            "group": "bottom",
+            "id": "42c0d925"
+          },
+          {
+            "group": "left",
+            "id": "58f52f2c"
+          }
         ]
       },
       "isCustom": true,
@@ -294,203 +462,437 @@
       "x": 42,
       "y": 6,
       "zIndex": 10
-    }
-  ],
-  "edges": [
+    },
     {
-      "id": "41561012:92334433-975bf288:15d1b217",
-      "source": { "cell": "41561012", "port": "92334433" },
-      "target": { "cell": "975bf288", "port": "15d1b217" },
-      "attrs": {
-        "line": {
-          "stroke": "#A2B1C3",
-          "targetMarker": { "name": "block", "width": 12, "height": 8 },
-          "strokeDasharray": [0, 0],
-          "strokeWidth": 1
-        }
-      }
+      "id": "2427bf29",
+      "renderKey": "custom-circle",
+      "name": "custom-circle",
+      "label": "三级审批1",
+      "width": 90,
+      "height": 90,
+      "ports": {
+        "items": [
+          {
+            "group": "top",
+            "id": "e5a149c4"
+          },
+          {
+            "group": "right",
+            "id": "e1a1ecea"
+          },
+          {
+            "group": "bottom",
+            "id": "6e131e6a"
+          },
+          {
+            "group": "left",
+            "id": "6bbf9ae4"
+          }
+        ]
+      },
+      "isCustom": true,
+      "parentKey": "1",
+      "x": -425,
+      "y": -224,
+      "zIndex": 10
     },
     {
-      "id": "975bf288:821f59c0-5764f3ce:c29d7b43",
-      "source": { "cell": "975bf288", "port": "821f59c0" },
-      "target": { "cell": "5764f3ce", "port": "c29d7b43" },
-      "attrs": {
-        "line": {
-          "stroke": "#A2B1C3",
-          "targetMarker": { "name": "block", "width": 12, "height": 8 },
-          "strokeDasharray": [0, 0],
-          "strokeWidth": 1
-        }
-      }
+      "id": "be25fe75",
+      "renderKey": "custom-circle",
+      "name": "custom-circle",
+      "label": "三级审批2",
+      "width": 90,
+      "height": 90,
+      "ports": {
+        "items": [
+          {
+            "group": "top",
+            "id": "13e4b9ea"
+          },
+          {
+            "group": "right",
+            "id": "ce651308"
+          },
+          {
+            "group": "bottom",
+            "id": "9a0b8942"
+          },
+          {
+            "group": "left",
+            "id": "a705e7ed"
+          }
+        ]
+      },
+      "isCustom": true,
+      "parentKey": "1",
+      "x": -425,
+      "y": -115,
+      "zIndex": 10
+    },
+    {
+      "id": "node-186a9d31-0bd3-4b36-b61f-6b5380c824db",
+      "renderKey": "custom-circle",
+      "name": "custom-circle",
+      "label": "审批节点1",
+      "width": 90,
+      "height": 90,
+      "ports": {
+        "items": [
+          {
+            "group": "top",
+            "id": "c6ec15a8-2db4-4ca8-ad81-fd1783b4ca0f"
+          },
+          {
+            "group": "right",
+            "id": "e134d65d-a197-4116-ad74-f47dbbb727d1"
+          },
+          {
+            "group": "bottom",
+            "id": "2c633cdf-7bb5-49ea-a4dc-e3d8c837e513"
+          },
+          {
+            "group": "left",
+            "id": "6d880b9d-7d7e-4357-94dd-caf7d73b5f80"
+          }
+        ]
+      },
+      "isCustom": true,
+      "parentKey": "1",
+      "x": 27,
+      "y": -224,
+      "zIndex": 10
     },
+    {
+      "id": "node-c5171e2d-1cd8-4019-83dc-9f2ed0cab6e8",
+      "renderKey": "custom-circle",
+      "name": "custom-circle",
+      "label": "审批节点2",
+      "width": 90,
+      "height": 90,
+      "ports": {
+        "items": [
+          {
+            "group": "top",
+            "id": "7b9fcd52-4fca-47c7-aaae-e3b117c0c234"
+          },
+          {
+            "group": "right",
+            "id": "6316d87e-2eb3-4ff6-bd8e-0cfd11bff4ce"
+          },
+          {
+            "group": "bottom",
+            "id": "38113945-040f-4374-aca8-38e2e81d71a8"
+          },
+          {
+            "group": "left",
+            "id": "8637b869-7924-416a-b938-30a7ca932901"
+          }
+        ]
+      },
+      "isCustom": true,
+      "parentKey": "1",
+      "x": 27,
+      "y": -115,
+      "zIndex": 10
+    },
+    {
+      "id": "node-2b3fc359-de49-4f9d-adb5-4eb70b6ba862",
+      "renderKey": "custom-circle",
+      "name": "custom-circle",
+      "label": "审批节点3",
+      "width": 90,
+      "height": 90,
+      "ports": {
+        "items": [
+          {
+            "group": "top",
+            "id": "d9b27b16-08ab-4e0d-84dc-fdec64c0a129"
+          },
+          {
+            "group": "right",
+            "id": "1ab6b2a4-dccd-41e9-83c6-d1642c9ad844"
+          },
+          {
+            "group": "bottom",
+            "id": "b763caac-37c1-4659-ae0e-c7b7c760fec1"
+          },
+          {
+            "group": "left",
+            "id": "2408c946-a670-48b5-a676-d882946b8e36"
+          }
+        ]
+      },
+      "isCustom": true,
+      "parentKey": "1",
+      "x": 173,
+      "y": -170,
+      "zIndex": 10
+    }
+  ],
+  "edges": [
     {
       "id": "975bf288:a61170c3-8c1f18d0:89c0bc16",
-      "source": { "cell": "975bf288", "port": "a61170c3" },
-      "target": { "cell": "8c1f18d0", "port": "89c0bc16" },
-      "attrs": {
-        "line": {
-          "stroke": "#A2B1C3",
-          "targetMarker": { "name": "block", "width": 12, "height": 8 },
-          "strokeDasharray": [0, 0],
-          "strokeWidth": 1
-        }
-      }
+      "source": {
+        "cell": "975bf288",
+        "port": "a61170c3"
+      },
+      "target": {
+        "cell": "8c1f18d0",
+        "port": "89c0bc16"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
     },
     {
       "id": "8c1f18d0:a3adcac9-41561012:293a90b5",
-      "source": { "cell": "8c1f18d0", "port": "a3adcac9" },
-      "target": { "cell": "41561012", "port": "293a90b5" },
-      "attrs": {
-        "line": {
-          "stroke": "#A2B1C3",
-          "targetMarker": { "name": "block", "width": 12, "height": 8 },
-          "strokeDasharray": [0, 0],
-          "strokeWidth": 1
-        }
-      }
+      "source": {
+        "cell": "8c1f18d0",
+        "port": "a3adcac9"
+      },
+      "target": {
+        "cell": "41561012",
+        "port": "293a90b5"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
     },
     {
       "id": "5764f3ce:4b4d9fa6-1aed14d1:d348b56a",
-      "source": { "cell": "5764f3ce", "port": "4b4d9fa6" },
-      "target": { "cell": "1aed14d1", "port": "d348b56a" },
-      "attrs": {
-        "line": {
-          "stroke": "#A2B1C3",
-          "targetMarker": { "name": "block", "width": 12, "height": 8 },
-          "strokeDasharray": "5 5",
-          "strokeWidth": 1
-        }
-      }
+      "source": {
+        "cell": "5764f3ce",
+        "port": "4b4d9fa6"
+      },
+      "target": {
+        "cell": "1aed14d1",
+        "port": "d348b56a"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":\"5 5\",\"strokeWidth\":1}}"
     },
     {
       "id": "1aed14d1:9a699e3f-4651130e:94f485b5",
-      "source": { "cell": "1aed14d1", "port": "9a699e3f" },
-      "target": { "cell": "4651130e", "port": "94f485b5" },
-      "attrs": {
-        "line": {
-          "stroke": "#A2B1C3",
-          "targetMarker": { "name": "block", "width": 12, "height": 8 },
-          "strokeDasharray": [0, 0],
-          "strokeWidth": 1
-        }
-      }
-    },
-    {
-      "id": "5764f3ce:ce88d7e2-3fb8d302:22d16375",
-      "source": { "cell": "5764f3ce", "port": "ce88d7e2" },
-      "target": { "cell": "3fb8d302", "port": "22d16375" },
-      "attrs": {
-        "line": {
-          "stroke": "#A2B1C3",
-          "targetMarker": { "name": "block", "width": 12, "height": 8 },
-          "strokeDasharray": [0, 0],
-          "strokeWidth": 1
-        }
-      }
+      "source": {
+        "cell": "1aed14d1",
+        "port": "9a699e3f"
+      },
+      "target": {
+        "cell": "4651130e",
+        "port": "94f485b5"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
     },
     {
       "id": "3fb8d302:205f1437-a48131e0:bac7962b",
-      "source": { "cell": "3fb8d302", "port": "205f1437" },
-      "target": { "cell": "a48131e0", "port": "bac7962b" },
-      "attrs": {
-        "line": {
-          "stroke": "#A2B1C3",
-          "targetMarker": { "name": "block", "width": 12, "height": 8 },
-          "strokeDasharray": [0, 0],
-          "strokeWidth": 1
-        }
-      }
+      "source": {
+        "cell": "3fb8d302",
+        "port": "205f1437"
+      },
+      "target": {
+        "cell": "a48131e0",
+        "port": "bac7962b"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
     },
     {
       "id": "a48131e0:4dee75d9-b57b57c8:73307680",
-      "source": { "cell": "a48131e0", "port": "4dee75d9" },
-      "target": { "cell": "b57b57c8", "port": "73307680" },
-      "attrs": {
-        "line": {
-          "stroke": "#A2B1C3",
-          "targetMarker": { "name": "block", "width": 12, "height": 8 },
-          "strokeDasharray": "5 5",
-          "strokeWidth": 1
-        }
-      }
+      "source": {
+        "cell": "a48131e0",
+        "port": "4dee75d9"
+      },
+      "target": {
+        "cell": "b57b57c8",
+        "port": "73307680"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":\"5 5\",\"strokeWidth\":1}}"
     },
     {
       "id": "a48131e0:0f72f2ba-3631eae9:2aae2a71",
-      "source": { "cell": "a48131e0", "port": "0f72f2ba" },
-      "target": { "cell": "3631eae9", "port": "2aae2a71" },
-      "attrs": {
-        "line": {
-          "stroke": "#A2B1C3",
-          "targetMarker": { "name": "block", "width": 12, "height": 8 },
-          "strokeDasharray": [0, 0],
-          "strokeWidth": 1
-        }
-      }
+      "source": {
+        "cell": "a48131e0",
+        "port": "0f72f2ba"
+      },
+      "target": {
+        "cell": "3631eae9",
+        "port": "2aae2a71"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
     },
     {
       "id": "3631eae9:8e0bff55-399bddb7:0bccd839",
-      "source": { "cell": "3631eae9", "port": "8e0bff55" },
-      "target": { "cell": "399bddb7", "port": "0bccd839" },
-      "attrs": {
-        "line": {
-          "stroke": "#A2B1C3",
-          "targetMarker": { "name": "block", "width": 12, "height": 8 },
-          "strokeDasharray": [0, 0],
-          "strokeWidth": 1
-        }
-      }
+      "source": {
+        "cell": "3631eae9",
+        "port": "8e0bff55"
+      },
+      "target": {
+        "cell": "399bddb7",
+        "port": "0bccd839"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
     },
     {
       "id": "399bddb7:58f52f2c-c28a18d3:e67dc19c",
-      "source": { "cell": "399bddb7", "port": "58f52f2c" },
-      "target": { "cell": "c28a18d3", "port": "e67dc19c" },
-      "attrs": {
-        "line": {
-          "stroke": "#A2B1C3",
-          "targetMarker": { "name": "block", "width": 12, "height": 8 },
-          "strokeDasharray": [0, 0],
-          "strokeWidth": 1
-        }
-      }
+      "source": {
+        "cell": "399bddb7",
+        "port": "58f52f2c"
+      },
+      "target": {
+        "cell": "c28a18d3",
+        "port": "e67dc19c"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
     },
     {
       "id": "c28a18d3:b578cc26-5359e23c:ff6724ee",
-      "source": { "cell": "c28a18d3", "port": "b578cc26" },
-      "target": { "cell": "5359e23c", "port": "ff6724ee" },
-      "attrs": {
-        "line": {
-          "stroke": "#A2B1C3",
-          "targetMarker": { "name": "block", "width": 12, "height": 8 },
-          "strokeDasharray": [0, 0],
-          "strokeWidth": 1
-        }
-      }
+      "source": {
+        "cell": "c28a18d3",
+        "port": "b578cc26"
+      },
+      "target": {
+        "cell": "5359e23c",
+        "port": "ff6724ee"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
     },
     {
       "id": "5359e23c:34c9dbc6-5216c5dc:d2030f1b",
-      "source": { "cell": "5359e23c", "port": "34c9dbc6" },
-      "target": { "cell": "5216c5dc", "port": "d2030f1b" },
-      "attrs": {
-        "line": {
-          "stroke": "#A2B1C3",
-          "targetMarker": { "name": "block", "width": 12, "height": 8 },
-          "strokeDasharray": [0, 0],
-          "strokeWidth": 1
-        }
-      }
+      "source": {
+        "cell": "5359e23c",
+        "port": "34c9dbc6"
+      },
+      "target": {
+        "cell": "5216c5dc",
+        "port": "d2030f1b"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
     },
     {
       "id": "b57b57c8:04c81e99-4651130e:1eb352b0",
-      "source": { "cell": "b57b57c8", "port": "04c81e99" },
-      "target": { "cell": "4651130e", "port": "1eb352b0" },
-      "attrs": {
-        "line": {
-          "stroke": "#A2B1C3",
-          "targetMarker": { "name": "block", "width": 12, "height": 8 },
-          "strokeDasharray": [0, 0],
-          "strokeWidth": 1
-        }
-      }
+      "source": {
+        "cell": "b57b57c8",
+        "port": "04c81e99"
+      },
+      "target": {
+        "cell": "4651130e",
+        "port": "1eb352b0"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
+    },
+    {
+      "id": "41561012:92334433-2427bf29:6bbf9ae4",
+      "source": {
+        "cell": "41561012",
+        "port": "92334433"
+      },
+      "target": {
+        "cell": "2427bf29",
+        "port": "6bbf9ae4"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
+    },
+    {
+      "id": "41561012:92334433-be25fe75:a705e7ed",
+      "source": {
+        "cell": "41561012",
+        "port": "92334433"
+      },
+      "target": {
+        "cell": "be25fe75",
+        "port": "a705e7ed"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
+    },
+    {
+      "id": "2427bf29:e1a1ecea-975bf288:15d1b217",
+      "source": {
+        "cell": "2427bf29",
+        "port": "e1a1ecea"
+      },
+      "target": {
+        "cell": "975bf288",
+        "port": "15d1b217"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
+    },
+    {
+      "id": "be25fe75:ce651308-975bf288:15d1b217",
+      "source": {
+        "cell": "be25fe75",
+        "port": "ce651308"
+      },
+      "target": {
+        "cell": "975bf288",
+        "port": "15d1b217"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
+    },
+    {
+      "id": "975bf288:821f59c0-5764f3ce:c29d7b43",
+      "source": {
+        "cell": "975bf288",
+        "port": "821f59c0"
+      },
+      "target": {
+        "cell": "5764f3ce",
+        "port": "c29d7b43"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
+    },
+    {
+      "id": "5764f3ce:ce88d7e2-node-186a9d31-0bd3-4b36-b61f-6b5380c824db:6d880b9d-7d7e-4357-94dd-caf7d73b5f80",
+      "source": {
+        "cell": "5764f3ce",
+        "port": "ce88d7e2"
+      },
+      "target": {
+        "cell": "node-186a9d31-0bd3-4b36-b61f-6b5380c824db",
+        "port": "6d880b9d-7d7e-4357-94dd-caf7d73b5f80"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
+    },
+    {
+      "id": "5764f3ce:ce88d7e2-node-c5171e2d-1cd8-4019-83dc-9f2ed0cab6e8:8637b869-7924-416a-b938-30a7ca932901",
+      "source": {
+        "cell": "5764f3ce",
+        "port": "ce88d7e2"
+      },
+      "target": {
+        "cell": "node-c5171e2d-1cd8-4019-83dc-9f2ed0cab6e8",
+        "port": "8637b869-7924-416a-b938-30a7ca932901"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
+    },
+    {
+      "id": "node-186a9d31-0bd3-4b36-b61f-6b5380c824db:e134d65d-a197-4116-ad74-f47dbbb727d1-node-2b3fc359-de49-4f9d-adb5-4eb70b6ba862:2408c946-a670-48b5-a676-d882946b8e36",
+      "source": {
+        "cell": "node-186a9d31-0bd3-4b36-b61f-6b5380c824db",
+        "port": "e134d65d-a197-4116-ad74-f47dbbb727d1"
+      },
+      "target": {
+        "cell": "node-2b3fc359-de49-4f9d-adb5-4eb70b6ba862",
+        "port": "2408c946-a670-48b5-a676-d882946b8e36"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
+    },
+    {
+      "id": "node-c5171e2d-1cd8-4019-83dc-9f2ed0cab6e8:6316d87e-2eb3-4ff6-bd8e-0cfd11bff4ce-node-2b3fc359-de49-4f9d-adb5-4eb70b6ba862:2408c946-a670-48b5-a676-d882946b8e36",
+      "source": {
+        "cell": "node-c5171e2d-1cd8-4019-83dc-9f2ed0cab6e8",
+        "port": "6316d87e-2eb3-4ff6-bd8e-0cfd11bff4ce"
+      },
+      "target": {
+        "cell": "node-2b3fc359-de49-4f9d-adb5-4eb70b6ba862",
+        "port": "2408c946-a670-48b5-a676-d882946b8e36"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
+    },
+    {
+      "id": "node-2b3fc359-de49-4f9d-adb5-4eb70b6ba862:1ab6b2a4-dccd-41e9-83c6-d1642c9ad844-3fb8d302:22d16375",
+      "source": {
+        "cell": "node-2b3fc359-de49-4f9d-adb5-4eb70b6ba862",
+        "port": "1ab6b2a4-dccd-41e9-83c6-d1642c9ad844"
+      },
+      "target": {
+        "cell": "3fb8d302",
+        "port": "22d16375"
+      },
+      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
     }
   ]
 }

+ 7 - 0
src/pages/PurchaseAdmin/PurchaseList/Flow/List.js

@@ -47,6 +47,13 @@ function List(props) {
     dispatch({
       type: 'flow/queryProject',
     });
+    // dispatch({
+    //   type: 'flow/getRoleList',
+    // });
+
+    // dispatch({
+    //   type: 'flow/queryDingTemplateList',
+    // });
   }, []);
 
   return (

+ 168 - 0
src/pages/PurchaseAdmin/PurchaseList/Flow/models/flow.js

@@ -6,20 +6,144 @@ import {
   queryBoomFlowDetail,
   updateNode,
   queryFlowList,
+  queryDingTemplateList,
+  saveAuditFlowInfo,
+  queryDepV2,
+  queryProcessFlows,
 } from '@/services/boom';
 import { queryRole } from '@/services/SysAdmin';
 import { queryProject } from '@/services/PurchaseList';
 import { message } from 'antd';
 
+function getDepUserTree(data) {
+  data.title = `${data.Name}`;
+  data.id = data.ID;
+  data.value = data.ID;
+  // data.selectable = false;
+  if (!data.children) data.children = new Array();
+
+  if (data.children) {
+    data.children.forEach(item => {
+      getDepUserTree(item);
+    });
+  }
+
+  if (data.Users && data.Users.length !== 0) {
+    data.Users.forEach(item => {
+      item.title = item.CName;
+      item.id = item.ID + '||' + data.ID;
+      item.value = item.ID + '||' + data.ID;
+      // item.selectable = true;
+      item.DepId = data.ID;
+      data.children.push(item);
+    });
+  }
+  return data;
+}
+
+const getFlowDetail = data => {
+  const groups = {
+    top: {
+      position: { name: 'top' },
+      attrs: {
+        circle: {
+          r: 4,
+          magnet: true,
+          stroke: '#31d0c6',
+          strokeWidth: 2,
+          fill: '#fff',
+          style: { visibility: 'hidden' },
+        },
+      },
+      zIndex: 10,
+    },
+    right: {
+      position: { name: 'right' },
+      attrs: {
+        circle: {
+          r: 4,
+          magnet: true,
+          stroke: '#31d0c6',
+          strokeWidth: 2,
+          fill: '#fff',
+          style: { visibility: 'hidden' },
+        },
+      },
+      zIndex: 10,
+    },
+    bottom: {
+      position: { name: 'bottom' },
+      attrs: {
+        circle: {
+          r: 4,
+          magnet: true,
+          stroke: '#31d0c6',
+          strokeWidth: 2,
+          fill: '#fff',
+          style: { visibility: 'hidden' },
+        },
+      },
+      zIndex: 10,
+    },
+    left: {
+      position: { name: 'left' },
+      attrs: {
+        circle: {
+          r: 4,
+          magnet: true,
+          stroke: '#31d0c6',
+          strokeWidth: 2,
+          fill: '#fff',
+          style: { visibility: 'hidden' },
+        },
+      },
+      zIndex: 10,
+    },
+  };
+  const attrs = {
+    line: {
+      stroke: '#A2B1C3',
+      targetMarker: { name: 'block', width: 12, height: 8 },
+      strokeDasharray: '5 5',
+      strokeWidth: 1,
+    },
+  };
+  let nodes = data.nodes.map(item => {
+    let node = { ...item };
+    node.ports.groups = groups;
+    node.parentKey = '1';
+
+    return node;
+  });
+  let edges = data.edges.map(item => {
+    let edge = { ...item };
+    try {
+      edge.attrs = item.attr ? JSON.parse(item.attr) : attrs;
+    } catch (error) {
+      edge.attrs = attrs;
+    }
+    return edge;
+  });
+  return {
+    ...data,
+    nodes,
+    edges,
+  };
+};
+
 export default {
   namespace: 'flow',
   state: {
     flowDetail: { nodes: [], edges: [] },
+    formData: {},
     auditList: [],
     flowList: [],
     projectList: [],
     current: {},
     roleList: [],
+    templateList: [],
+    depUserTree: [],
+    simpleFlowDteail: '',
   },
 
   effects: {
@@ -100,6 +224,50 @@ export default {
         callback && callback();
       }
     },
+    *queryDingTemplateList({ payload }, { call, put }) {
+      const response = yield call(queryDingTemplateList, payload);
+      if (response) {
+        yield put({
+          type: 'save',
+          payload: { templateList: response.data.result },
+        });
+      }
+    },
+    *saveAuditFlowInfo({ payload, callback }, { call, put }) {
+      const response = yield call(saveAuditFlowInfo, payload);
+      if (response) {
+        message.success('保存成功');
+        callback && callback();
+      }
+    },
+    *fetchDepV2({ payload, callback }, { call, put }) {
+      const response = yield call(queryDepV2, { pageSize: 999999 });
+      if (response) {
+        // const depUserTree = response.data?.list;
+        const depUserTree = response.data.list.map(item => {
+          return getDepUserTree(item);
+        });
+        yield put({
+          type: 'save',
+          payload: { depUserTree },
+        });
+      }
+    },
+    *queryProcessFlows({ payload }, { call, put }) {
+      const data = yield call(queryProcessFlows, payload);
+      if (data && data.length > 0) {
+        yield put({
+          type: 'save',
+          payload: {
+            flowDetail: data[0].process_json
+              ? getFlowDetail(JSON.parse(data[0].process_json))
+              : { nodes: [], edges: [] },
+            formData: data[0].form_json ? JSON.parse(data[0].form_json) : [],
+            simpleFlowDteail: data[0].process_simple_json,
+          },
+        });
+      }
+    },
   },
 
   reducers: {

+ 32 - 18
src/pages/PurchaseAdmin/PurchaseList/Index.js

@@ -25,6 +25,20 @@ function LayoutDetail(props) {
       type: 'user/fetchCurrent',
     });
   }, []);
+  const checkReport = state => {
+    if (isAdmin) return true;
+    const manager = currentUser.is_leader || currentUser.is_opt_mgr || currentUser.is_wty_mgr;
+    switch (state) {
+      case 0:
+        return currentUser.is_accountant || manager || permission['func-01-point-works-report'];
+      case 1:
+        return manager || permission['func-01-point-works-report-p'];
+      case 2:
+        return permission['func-01-point-works-report-d'];
+      case 3:
+        return currentUser.is_accountant || permission['func-01-point-works-report-p-s'];
+    }
+  };
   return (
     <Layout>
       <Header>
@@ -37,7 +51,7 @@ function LayoutDetail(props) {
               defaultSelectedKeys={[props.location.pathname]}
               style={{ lineHeight: '64px', width: '100%' }}
             >
-              {/* <SubMenu key="/home/work-hours" title="工时管理">
+              <SubMenu key="/home/work-hours" title="工时管理">
                 <Menu.Item key="/home/work-hours">
                   <Link to="/home/work-hours">上报工时</Link>
                 </Menu.Item>
@@ -54,33 +68,33 @@ function LayoutDetail(props) {
                   <Link to="/home/approval/auth">审核列表</Link>
                 </Menu.Item>
               </SubMenu>
-      
-              {(isAdmin ||
-                currentUser.is_leader ||
-                currentUser.is_accountant ||
-                permission['func-01-point-works-report']) && (
+
+              {checkReport(0) && (
                 <SubMenu key="/home/report" title="工时报表">
-                  {(isAdmin ||
-                    currentUser.is_leader ||
-                    currentUser.is_accountant ||
-                    permission['func-01-point-works-report-p'] ||
-                    permission['func-01-point-works-report-p-s']) && (
+                  {/* <Menu.Item key="/home/report/resource">
+                  <Link to="/home/report/resource">资源报表</Link>
+                </Menu.Item> */}
+                  {checkReport(1) && (
                     <Menu.Item key="/home/report/project">
                       <Link to="/home/report/project">项目报表</Link>
                     </Menu.Item>
                   )}
-                  {(isAdmin || permission['func-01-point-works-report-d']) && (
+                  {checkReport(2) && (
                     <Menu.Item key="/home/report/department">
                       <Link to="/home/report/department">部门报表</Link>
                     </Menu.Item>
                   )}
+                  {checkReport(3) && (
+                    <Menu.Item key="/home/report/finance">
+                      <Link to="/home/report/finance">财务报表</Link>
+                    </Menu.Item>
+                  )}
                 </SubMenu>
-              )} */}
-              {/* {isAdmin && ( */}
-                <Menu.Item key="/home">
-                  <Link to="/home">采购清单</Link>
-                </Menu.Item>
-              {/* )} */}
+              )}
+
+              <Menu.Item key="/home">
+                <Link to="/home">采购清单</Link>
+              </Menu.Item>
               <Menu.Item key="/home/flow-list">
                 <Link to="/home/flow-list">流程图</Link>
               </Menu.Item>

+ 104 - 6
src/pages/PurchaseAdmin/PurchaseList/List/NewList.js

@@ -1,10 +1,22 @@
 import React, { useState, useEffect } from 'react';
-import { Table, Divider } from 'antd';
+import { Table, Divider, message } from 'antd';
 import { connect } from 'dva';
 import router from 'umi/router';
+import FlowModal from '../Detail/FlowModal';
+import { queryBoomFlowDetail, queryRecordSheet } from '@/services/boom';
+import { async } from '@antv/x6/es/registry/marker/async';
+import { getToken } from '@/utils/utils';
+import AuditForm from '@/components/AuditForm';
 
 function List(props) {
-  const { excel, loading, project, dispatch } = props;
+  const { excel, loading, project, dispatch, versionList } = props;
+  const [flowVisible, setFlowVisible] = useState(false);
+  const [version, setVersion] = useState({});
+  const [versionVisible, setVersionVisible] = useState(false);
+  const [flowDetail, setFlowDetail] = useState();
+  const [loading2, setLoading2] = useState(false);
+
+  let token = getToken();
 
   const columns = [
     {
@@ -26,9 +38,18 @@ function List(props) {
         if (record.is_parent) return null;
         return (
           <a
-            onClick={() => {
+            onClick={async () => {
               localStorage.excelId = record.id;
-              router.push(`/home/detail/${record.project_id}/${record.template_id}`);
+
+              setLoading2(true);
+              try {
+                const data = await queryBoomFlowDetail({ id: record.template_id });
+                setFlowDetail(data);
+                setVersion(record);
+                setFlowVisible(true);
+              } catch (error) {}
+              setLoading2(false);
+              // router.push(`/home/detail/${record.project_id}/${record.template_id}`);
             }}
           >
             查看
@@ -58,24 +79,101 @@ function List(props) {
     dispatch({
       type: 'newList/queryProject',
     });
+    dispatch({
+      type: 'newList/queryVersionsList',
+    });
+
+    dispatch({
+      type: 'user/queryDepV2',
+    });
   }, []);
 
+  const changeVersion = item => {
+    if (typeof item == 'object') {
+      localStorage.excelItem = JSON.stringify(item);
+    }
+    router.push(`/home/detail/${item.project_id}/${item.template_id}`);
+  };
+
+  const getLoading = () => {
+    let effects = loadingVersion.effects;
+    return !loadingVersion.effects['detail/queryComment'] && loadingVersion.models.detail;
+  };
+
+  const onCommit = async (values, id) => {
+    let currentNode = flowDetail.nodes.find?.(item => item.Id == version.template_node_id);
+    let sheets = await queryRecordSheet({ gridKey: version.id, 'JWT-TOKEN': token });
+    // if (!currentNode.muti_version) {
+    //   // audit_status=4 表示为清单推进后留存的副本.不计入多清单计算
+    //   let serviceVersion = versionList?.find(
+    //     item => item.audit_status != 4 && item.template_node_id == currentNode.Id
+    //   );
+    //   if (serviceVersion) {
+    //     message.error(`新建清单失败!业务节点【${currentNode.label}】只能有一个清单!`);
+    //     return;
+    //   }
+    // }
+    let params = {
+      ...values,
+      id: id,
+      project_id: version.project_id,
+      name: version.name,
+      guid: version.guid,
+      template_id: version.template_id,
+      template_node_id: version.template_node_id,
+      flow_id: version.flow_id,
+      node_id: version.node_id,
+      new_version: '0',
+      audit_status: 0,
+      data: sheets,
+      base_id: version.id,
+    };
+    dispatch({
+      type: 'newList/commitSheet',
+      payload: params,
+      callback: async newVersion => {
+        // 更新flow流程图
+        const data = await queryBoomFlowDetail({ id: newVersion.template_id });
+        console.log(data);
+        setFlowDetail(data);
+      },
+    });
+  };
+
   return (
     <div>
+      {/* <AuditForm onChange={values => console.log(values)} /> */}
       <Table
-        loading={loading}
+        loading={loading || loading2}
         rowKey="id"
         dataSource={excel.list}
         pagination={excel.pagination}
         columns={columns}
         onChange={queryList}
       />
+      <FlowModal
+        isOut={true}
+        flowDetail={flowDetail}
+        visible={flowVisible}
+        onClose={() => setFlowVisible(false)}
+        version={version}
+        onCommit={onCommit}
+        onChangeVersion={version => changeVersion(version)}
+      />
+      {/* <VersionModal
+        loading={getLoading()}
+        visible={versionVisible}
+        onClose={() => setVersionVisible(false)}
+        onOk={values => onCommit(values)}
+      /> */}
     </div>
   );
 }
 
-export default connect(({ newList, loading }) => ({
+export default connect(({ newList, loading, detail }) => ({
   excel: newList.excel,
   project: newList.project,
   loading: loading.models.newList,
+  loadingVersion: loading,
+  versionList: newList.versionList,
 }))(List);

+ 16 - 4
src/pages/PurchaseAdmin/PurchaseList/List/models/list.js

@@ -15,9 +15,8 @@ import {
   Logout,
   queryProject,
 } from '@/services/PurchaseList';
-import {
-  queryRole
-} from '@/services/SysAdmin';
+import { queryVersionsList } from '@/services/boom';
+import { queryRole } from '@/services/SysAdmin';
 import { setCurrentUser } from '@/utils/authority';
 import { storeToken } from '@/utils/utils';
 import { routerRedux } from 'dva/router';
@@ -47,6 +46,7 @@ export default {
     },
     comment: { list: [] },
     roleList: [],
+    versionList: [],
   },
 
   effects: {
@@ -117,7 +117,7 @@ export default {
       }
     },
     *queryHistory({ payload, callback }, { call, put }) {
-      payload.pageSize = '9999'
+      payload.pageSize = '9999';
       const response = yield call(queryHistory, payload);
       if (response) {
         yield put({
@@ -282,6 +282,18 @@ export default {
         });
       }
     },
+    *queryVersionsList({ payload, callback }, { call, put }) {
+      const response = yield call(queryVersionsList, payload);
+      if (response) {
+        callback && callback(response.data);
+        yield put({
+          type: 'save',
+          payload: {
+            versionList: response.data,
+          },
+        });
+      }
+    },
   },
 
   reducers: {

+ 46 - 3
src/pages/PurchaseAdmin/PurchaseList/List/models/newList.js

@@ -1,8 +1,7 @@
 import { queryProjectRecord } from '@/services/boom';
-import {
-  queryProject,
-} from '@/services/PurchaseList';
+import { queryProject } from '@/services/PurchaseList';
 import { message } from 'antd';
+import { commitSheet, queryVersionsList, queryAuditExcel, queryAuditRecord } from '@/services/boom';
 
 export default {
   namespace: 'newList',
@@ -15,6 +14,7 @@ export default {
       list: [],
       pagination: false,
     },
+    versionList: [],
   },
 
   effects: {
@@ -50,6 +50,49 @@ export default {
         });
       }
     },
+    *queryVersionsList({ payload, callback }, { call, put }) {
+      const response = yield call(queryVersionsList, payload);
+      if (response) {
+        callback && callback(response.data);
+        yield put({
+          type: 'save',
+          payload: {
+            versionList: response.data,
+          },
+        });
+      }
+    },
+    *commitSheet({ payload, callback }, { call, put }) {
+      let response = yield call(commitSheet, payload);
+      if (response) {
+        message.success('提交成功');
+        yield put({
+          type: 'queryVersionsList',
+          payload: {
+            project_id: payload.project_id,
+            // template_id: payload.template_id,
+            // template_node_id: payload.template_node_id,
+          },
+          callback: versionList => {
+            let newVersion = versionList.find(item => item.id == response.data.id);
+            callback && callback(newVersion);
+          },
+        });
+      }
+    },
+
+    *queryAuditExcel({ payload, callback }, { call, put }) {
+      const response = yield call(queryAuditExcel, payload);
+      if (response) {
+        callback && callback(response.data?.all);
+      }
+    },
+    *queryAuditRecord({ payload, callback }, { call, put }) {
+      const response = yield call(queryAuditRecord, payload);
+      if (response) {
+        callback && callback(response.data?.all);
+      }
+    },
   },
 
   reducers: {

+ 31 - 41
src/pages/PurchaseAdmin/PurchaseList/Report/Department.js

@@ -1,23 +1,24 @@
 import React, { useEffect, useState, useRef } from 'react';
 import { connect } from 'dva';
-import { Form } from '@ant-design/compatible';
-import '@ant-design/compatible/assets/index.css';
-import { Table, DatePicker, Input, Button } from 'antd';
+import { Form, Table, DatePicker, Input, Button } from 'antd';
 import styles from './report.less';
 import UserRptModal from './UserRptModal';
+import DepCompareModal from './DepCompareModal';
 import moment from 'moment';
-import { downloadFile, getToken } from '@/utils/utils.js'
+import { downloadFile, getToken } from '@/utils/utils.js';
 
 const { RangePicker } = DatePicker;
 
 function Department(props) {
-  const { dispatch, form, loading, dep } = props;
+  const { dispatch, loading, dep } = props;
+  const [form] = Form.useForm();
   const [visible, setVisible] = useState(false);
   const [modalFilter, setModalFilter] = useState({});
   const columns = [
     {
       title: '部门名称',
-      render: record => <a onClick={() => showUserModal(record)}>{record.dep_name}</a>,
+      // render: record => <a onClick={() => showUserModal(record)}>{record.dep_name}</a>,
+      render: record => <a onClick={() => showDepCompare(record)}>{record.dep_name}</a>,
     },
     {
       title: '执行项目人日',
@@ -67,46 +68,32 @@ function Department(props) {
     // },
   ];
   const filterRef = useRef({ pageSize: 99999 });
-  const onChangePage = pagination => {
+  const handleSearch = () => {
+    const { time } = form.getFieldsValue();
+    filterRef.current.s_time = time[0] ? moment(time[0]).format('YYYY-MM-DD') : null;
+    filterRef.current.e_time = time[1] ? moment(time[1]).format('YYYY-MM-DD') : null;
+
     dispatch({
-      type: 'report/queryUserReport',
+      type: 'report/queryDepReport',
       payload: {
-        ...filterRef.current,
-        currentPage: pagination.current,
+        filter: filterRef.current,
       },
     });
   };
-  const handleSearch = () => {
-    form.validateFields((error, { time }) => {
-      filterRef.current.s_time = time[0] ? moment(time[0]).format('YYYY-MM-DD') : null;
-      filterRef.current.e_time = time[1] ? moment(time[1]).format('YYYY-MM-DD') : null;
-
-      dispatch({
-        type: 'report/queryDepReport',
-        payload: {
-          filter: filterRef.current,
-        },
-      });
-    });
-  };
   const handleDownload = () => {
     const token = getToken();
-    const s_time = !(filterRef.current.s_time) ? '' : filterRef.current.s_time;
-    const e_time = !(filterRef.current.e_time) ? '' : filterRef.current.e_time;
-    downloadFile(`/api/v2/workload/rpt/dep/export2excel?JWT-TOKEN=${token}&s_time=${s_time}&e_time=${e_time}`, `项目报表${moment().format('YYYYMMDDHHMMSS')}.xlsx`)
+    const s_time = !filterRef.current.s_time ? '' : filterRef.current.s_time;
+    const e_time = !filterRef.current.e_time ? '' : filterRef.current.e_time;
+    downloadFile(
+      `/api/v2/workload/rpt/dep/export2excel?JWT-TOKEN=${token}&s_time=${s_time}&e_time=${e_time}`,
+      `项目报表${moment().format('YYYYMMDDHHMMSS')}.xlsx`
+    );
   };
   const renderSearch = () => {
-    const formItemLayout = {
-      labelCol: { span: 5 },
-      wrapperCol: { span: 18 },
-    };
-
     return (
-      <Form layout="inline" {...formItemLayout}>
-        <Form.Item label="时间">
-          {form.getFieldDecorator('time', {
-            initialValue: [moment().startOf('years'), moment().endOf('years')],
-          })(<RangePicker placeholder="选择时间" />)}
+      <Form layout="inline" form={form}>
+        <Form.Item label="时间" name="time" initialValue={[moment().startOf('years'), moment()]}>
+          <RangePicker placeholder="选择时间" allowClear={false} />
         </Form.Item>
         <Form.Item>
           <Button type="primary" loading={loading} onClick={handleSearch}>
@@ -127,7 +114,8 @@ function Department(props) {
       });
     }
   };
-  const showUserModal = item => {
+  // const showUserModal = item => {
+  const showDepCompare = item => {
     const { s_time, e_time } = filterRef.current;
     setModalFilter({
       s_time: s_time,
@@ -147,7 +135,9 @@ function Department(props) {
     <div>
       <div className={styles.topPart}>
         {renderSearch()}
-        <Button type="primary" onClick={handleDownload}>导出</Button>
+        <Button type="primary" onClick={handleDownload}>
+          导出
+        </Button>
       </div>
       <Table
         loading={loading}
@@ -156,10 +146,10 @@ function Department(props) {
         columns={columns}
         dataSource={dep.list}
         pagination={false}
-        onChange={onChangePage}
         onExpand={onExpand}
       />
-      <UserRptModal filter={modalFilter} visible={visible} onCancel={() => setVisible(false)} />
+      {/* <UserRptModal filter={modalFilter} visible={visible} onCancel={() => setVisible(false)} /> */}
+      <DepCompareModal filter={modalFilter} visible={visible} onCancel={() => setVisible(false)} />
     </div>
   );
 }
@@ -167,4 +157,4 @@ function Department(props) {
 export default connect(({ report, loading }) => ({
   dep: report.dep,
   loading: loading.models.report,
-}))(Form.create()(Department));
+}))(Department);

+ 137 - 112
src/pages/PurchaseAdmin/PurchaseList/Report/Project.js

@@ -1,8 +1,6 @@
 import React, { useEffect, useState, useRef } from 'react';
 import { connect } from 'dva';
-import { Form } from '@ant-design/compatible';
-import '@ant-design/compatible/assets/index.css';
-import { Table, DatePicker, Input, Button, Select, message } from 'antd';
+import { Form, Table, DatePicker, Input, Button, Select, message, Popover } from 'antd';
 import report from './models/report';
 import styles from './report.less';
 import moment from 'moment';
@@ -11,7 +9,7 @@ import { downloadFile, getToken } from '@/utils/utils.js';
 
 const { Option } = Select;
 const { RangePicker } = DatePicker;
-const initDate = [moment().startOf('years'), moment().endOf('years')];
+const initDate = [moment().startOf('years'), moment()];
 
 // var currentYear = new Date().getFullYear();
 // var yearList = [];
@@ -21,20 +19,21 @@ const initDate = [moment().startOf('years'), moment().endOf('years')];
 // }
 
 function Resource(props) {
-  const { dispatch, form, loading, project } = props;
+  const { dispatch, loading, project } = props;
+  const [form] = Form.useForm();
   const [expandedRowKeys, setExpandedRowKeys] = useState([]);
   const [visible, setVisible] = useState(false);
   const [modalFilter, setModalFilter] = useState({});
   const filterRef = useRef({});
-  const onChangePage = pagination => {
-    dispatch({
-      type: 'report/queryProjectReport',
-      payload: {
-        ...filterRef.current,
-        currentPage: pagination.current,
-      },
-    });
-  };
+  // const onChangePage = pagination => {
+  //   dispatch({
+  //     type: 'report/queryProjectReport',
+  //     payload: {
+  //       ...filterRef.current,
+  //       currentPage: pagination.current,
+  //     },
+  //   });
+  // };
   const getMonthColumns = () => {
     let arr = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二'];
     var time;
@@ -63,65 +62,72 @@ function Resource(props) {
       date[cYear].push(arr[cMonth]);
       current.add('month', 1);
     }
-    console.log(date);
     current.subtract('month', 12);
     let monthColumns = Object.keys(date).map(year => ({
       title: year + '年',
-      children: date[year].map((item) => {
+      children: date[year].map(item => {
         let key = current.format('YYYY-MM');
         current.add('month', 1);
         return {
           title: `${item}`,
           render: record => {
             const { month } = record;
-            return month[key]?.total_audit_cnt || 0;
+            return month.find(item => item.st == key)?.pass_audit_cnt || 0;
+            // return month[key]?.total_audit_cnt || 0;
+            // return (
+            //   <Popover
+            //     content={
+            //       <>
+            //         {`审核通过: ${month[key]?.pass_audit_cnt || 0}`}
+            //         <br />
+            //         {`审核中: ${month[key]?.pending_audit_cnt || 0}`}
+            //         <br />
+            //         {`审核拒绝: ${month[key]?.refuse_audit_cnt || 0}`}
+            //         <br />
+            //         {`未提审: ${month[key]?.refuse_audit_cnt || 0}`}
+            //       </>
+            //     }
+            //   >
+            //     {month[key]?.total_audit_cnt || 0}
+            //   </Popover>
+            // );
             // return `${JSON.stringify(month)}`;
           },
         };
       }),
     }));
     return monthColumns;
-
-    // return {
-    //   title: '月份',
-    //   children: arr.map((item, index) => ({
-    //     title: `${item}`,
-    //     dataIndex: `month[${String(index)}].total_audit_cnt`,
-    //     render: cnt => cnt || 0,
-    //   })),
-    // };
-  };
-  const onExpand = (expanded, record) => {
-    if (expanded && !record.isLoad) {
-      dispatch({
-        type: 'report/queryProjectReportDetail',
-        payload: {
-          ...filterRef.current,
-          record: record,
-        },
-      });
-    }
   };
+  // const onExpand = (expanded, record) => {
+  //   if (expanded && !record.isLoad) {
+  //     dispatch({
+  //       type: 'report/queryProjectReportDetail',
+  //       payload: {
+  //         ...filterRef.current,
+  //         record: record,
+  //       },
+  //     });
+  //   }
+  // };
 
   const handleSearch = () => {
-    form.validateFields((error, { time, project_name }) => {
-      if (
-        !moment(time[0])
-          .add(12, 'month')
-          .isSameOrAfter(time[1])
-      ) {
-        message.error('时间间隔超过12个月,请重新选择。');
-        return;
-      }
-      filterRef.current.s_time = time[0] ? moment(time[0]).format('YYYY-MM-DD') : null;
-      filterRef.current.e_time = time[1] ? moment(time[1]).format('YYYY-MM-DD') : null;
-      dispatch({
-        type: 'report/queryProjectReport',
-        payload: {
-          ...filterRef.current,
-          project_name: project_name,
-        },
-      });
+    const { time, project_name } = form.getFieldsValue();
+    if (
+      !moment(time[0])
+        .add(12, 'month')
+        .isSameOrAfter(time[1])
+    ) {
+      message.error('时间间隔超过12个月,请重新选择。');
+      return;
+    }
+    filterRef.current.s_time = time[0] ? moment(time[0]).format('YYYY-MM-DD') : null;
+    filterRef.current.e_time = time[1] ? moment(time[1]).format('YYYY-MM-DD') : null;
+    dispatch({
+      type: 'report/queryProjectReport',
+      payload: {
+        ...filterRef.current,
+        project_name: project_name,
+      },
     });
   };
   const handleDownload = () => {
@@ -138,21 +144,12 @@ function Resource(props) {
   };
   const renderSearch = () => {
     return (
-      <Form layout="inline">
-        <Form.Item label="时间">
-          {form.getFieldDecorator('time', {
-            initialValue: initDate,
-          })(<RangePicker placeholder="选择时间" />)}
+      <Form layout="inline" form={form}>
+        <Form.Item label="时间" name="time" initialValue={initDate}>
+          <RangePicker placeholder="选择时间" allowClear={false} />
         </Form.Item>
-        <Form.Item label="项目">
-          {form.getFieldDecorator('project_name')(
-            <Input />
-            // <Select allowClear showSearch style={{ width: 240 }} placeholder="请选择项目" >
-            //   {projectList.map(item => (
-            //     <Option key={item.ID} value={item.Name}>{item.Name}</Option>
-            //   ))}
-            // </Select>
-          )}
+        <Form.Item label="项目" name="project_name">
+          <Input />
         </Form.Item>
         <Form.Item>
           <Button type="primary" loading={loading} onClick={handleSearch}>
@@ -166,61 +163,89 @@ function Resource(props) {
     {
       title: '项目名称',
       render: record =>
-        !record.isParent ? (
-          <a onClick={() => showUserModal(record)}>{record.type_name}</a>
+        !record.child && record.total_audit_cnt ? (
+          <a onClick={() => showUserModal(record)}>{record.name}</a>
         ) : (
-          record.type_name
+          record.name
         ),
     },
-    {
-      title: '分项工作代码',
-      align: 'center',
-      dataIndex: 'type_code',
-    },
-    // {
-    //   title: '项目名称',
-    //   render: record => (record.project_id == 0 ? '' : record.project_name),
-    // },
-    // {
-    //   title: '项目编号',
-    //   dataIndex: 'project_code',
-    // },
-    // {
-    //   title: '工作内容',
-    //   dataIndex: 'type_name',
-    // },
     ...getMonthColumns(),
-    {
-      title: '总计',
-      dataIndex: 'month_rpt',
-      render: arr => (arr ? arr.reduce((total, item) => total + item.total_audit_cnt, 0) : ''),
-    },
+    { title: '总计', dataIndex: 'total_audit_cnt' },
   ];
+  // const columns = [
+  //   {
+  //     title: '项目名称',
+  //     render: record =>
+  //       !record.isParent ? (
+  //         <a onClick={() => showUserModal(record)}>{record.type_name}</a>
+  //       ) : (
+  //         record.type_name
+  //       ),
+  //   },
+  //   {
+  //     title: '分项工作代码',
+  //     align: 'center',
+  //     dataIndex: 'type_code',
+  //   },
+  //   // {
+  //   //   title: '项目名称',
+  //   //   render: record => (record.project_id == 0 ? '' : record.project_name),
+  //   // },
+  //   // {
+  //   //   title: '项目编号',
+  //   //   dataIndex: 'project_code',
+  //   // },
+  //   // {
+  //   //   title: '工作内容',
+  //   //   dataIndex: 'type_name',
+  //   // },
+  //   ...getMonthColumns(),
+  //   {
+  //     title: '总计',
+  //     dataIndex: 'month_rpt',
+  //     render: arr => (
+  //       <Popover
+  //         content={
+  //           <>
+  //             {`审核通过: ${
+  //               arr ? arr.reduce((total, item) => total + item.pass_audit_cnt, 0) : ''
+  //             }`}
+  //             <br />
+  //             {`审核中: ${
+  //               arr ? arr.reduce((total, item) => total + item.pending_audit_cnt, 0) : ''
+  //             }`}
+  //             <br />
+  //             {`审核拒绝: ${
+  //               arr ? arr.reduce((total, item) => total + item.refuse_audit_cnt, 0) : ''
+  //             }`}
+  //             <br />
+  //             {`未提审: ${arr ? arr.reduce((total, item) => total + item.un_audit_cnt, 0) : ''}`}
+  //           </>
+  //         }
+  //       >
+  //         {arr ? arr.reduce((total, item) => total + item.total_audit_cnt, 0) : ''}
+  //       </Popover>
+  //     ),
+  //   },
+  // ];
   const showUserModal = item => {
     const { s_time, e_time } = filterRef.current;
     setModalFilter({
       s_time: s_time,
       e_time: e_time,
+      type_id: item.id,
       project_id: item.project_id,
-      p_type_id: item.project_id == 0 ? item.p_type_id : 0,
-      type_id: item.type_id,
+      flag: item.flag,
     });
     setVisible(true);
   };
   useEffect(() => {
-    // dispatch({
-    //   type: 'report/queryProjectReport',
-    // });
-    // dispatch({
-    //   type: 'report/queryProject',
-    // });
     handleSearch();
   }, []);
 
-  useEffect(() => {
-    setExpandedRowKeys(project.list.map(item => item.id));
-  }, [project.list]);
-  console.log(project.list)
+  // useEffect(() => {
+  //   setExpandedRowKeys(project.list.map(item => item.id));
+  // }, [project.list]);
 
   return (
     <div className={styles.page}>
@@ -233,11 +258,11 @@ function Resource(props) {
       <Table
         style={{ marginTop: 20 }}
         columns={columns}
-        rowKey="id"
-        onExpand={onExpand}
-        dataSource={project.list}
-        pagination={project.pagination}
-        onChange={onChangePage}
+        rowKey="key"
+        childrenColumnName="child"
+        // onExpand={onExpand}
+        dataSource={project}
+        pagination={false}
       />
       <UserProjectRptModal
         filter={modalFilter}
@@ -252,4 +277,4 @@ export default connect(({ report, loading }) => ({
   project: report.project,
   // projectList: report.projectList,
   loading: loading.models.report,
-}))(Form.create()(Resource));
+}))(Resource);

+ 1 - 3
src/pages/PurchaseAdmin/PurchaseList/Report/Resource.js

@@ -1,8 +1,6 @@
 import React, { useEffect, useState, useRef } from 'react';
 import { connect } from 'dva';
-import { Form } from '@ant-design/compatible';
-import '@ant-design/compatible/assets/index.css';
-import { Table, DatePicker, Input, Button } from 'antd';
+import { Form, Table, DatePicker, Input, Button } from 'antd';
 import report from './models/report';
 import moment from 'moment';
 

+ 55 - 21
src/pages/PurchaseAdmin/PurchaseList/Report/UserProjectRptModal.js

@@ -1,23 +1,55 @@
 import React, { useState, useEffect } from 'react';
-import { Form } from '@ant-design/compatible';
-import '@ant-design/compatible/assets/index.css';
-import { Modal, Table, Input, Button } from 'antd';
+import { Modal, Table, Form, Input, Button } from 'antd';
 import { connect } from 'dva';
 import moment from 'moment';
 
-function AddModal(props) {
-  const { dispatch, visible, onOk, onCancel, data, filter, loading, form } = props;
+function UserProjectRptModal(props) {
+  const { dispatch, visible, onOk, onCancel, data, filter, loading } = props;
+  const [form] = Form.useForm();
+  // const getMonthColumns = () => {
+  //   let arr = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二'];
+
+  //   return {
+  //     title: '月份',
+  //     children: arr.map((item, index) => ({
+  //       title: `${item}`,
+  //       dataIndex: `month[${String(index)}].total_audit_cnt`,
+  //       render: cnt => cnt || 0,
+  //     })),
+  //   };
+  // };
   const getMonthColumns = () => {
     let arr = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二'];
+    var time = [moment(filter.s_time), moment(filter.e_time)];
+    var date = {};
+    let eYear = time[1].year();
+    let eMonth = time[1].month();
+    let current = moment(time[0]);
+    let cYear, cMonth;
 
-    return {
-      title: '月份',
-      children: arr.map((item, index) => ({
-        title: `${item}`,
-        dataIndex: `month[${String(index)}].total_audit_cnt`,
-        render: cnt => cnt || 0,
-      })),
-    };
+    for (let i = 0; i < 12; i++) {
+      cYear = current.year();
+      cMonth = current.month();
+      if (!date[cYear]) date[cYear] = [];
+      date[cYear].push(arr[cMonth]);
+      current.add('month', 1);
+    }
+    current.subtract('month', 12);
+    let monthColumns = Object.keys(date).map(year => ({
+      title: year + '年',
+      children: date[year].map(item => {
+        let key = current.format('YYYY-MM');
+        current.add('month', 1);
+        return {
+          title: `${item}`,
+          render: record => {
+            const { month_rpt } = record;
+            return month_rpt.find(item => item.ts == key)?.pass_audit_cnt || 0;
+          },
+        };
+      }),
+    }));
+    return monthColumns;
   };
   const columns = [
     {
@@ -28,11 +60,12 @@ function AddModal(props) {
       title: '部门',
       dataIndex: 'dep_name',
     },
-    getMonthColumns(),
+    // getMonthColumns(),
+    ...getMonthColumns(),
     {
       title: '总计',
       dataIndex: 'month_rpt',
-      render: arr => (arr ? arr.reduce((total, item) => total + item.total_audit_cnt, 0) : ''),
+      render: arr => (arr ? arr.reduce((total, item) => total + item.pass_audit_cnt, 0) : ''),
     },
   ];
   const onChangePage = pagination => {
@@ -45,7 +78,8 @@ function AddModal(props) {
     });
   };
   const handleSearch = () => {
-    if (!filter?.project_id && !filter?.p_type_id) return;
+    // if (!filter?.project_id && !filter?.p_type_id) return;
+    if (!filter?.classification) return;
     const dep_name = form.getFieldValue('dep_name');
     dispatch({
       type: 'report/queryUserProjectReport',
@@ -56,7 +90,7 @@ function AddModal(props) {
     });
   };
   useEffect(() => {
-    if (!filter?.project_id && !filter?.p_type_id) return;
+    if (!filter?.project_id) return;
     dispatch({
       type: 'report/queryUserProjectReport',
       payload: filter,
@@ -65,9 +99,9 @@ function AddModal(props) {
 
   return (
     <Modal title="工时" width="80%" visible={visible} onCancel={onCancel} footer={false}>
-      <Form layout="inline" style={{ marginBottom: 20 }}>
-        <Form.Item lable="部门">
-          {form.getFieldDecorator('dep_name')(<Input placeholder="请输入部门" />)}
+      <Form layout="inline" style={{ marginBottom: 20 }} form={form}>
+        <Form.Item lable="部门" name="dep_name">
+          <Input placeholder="请输入部门" />
         </Form.Item>
         <Form.Item>
           <Button type="primary" onClick={handleSearch}>
@@ -89,4 +123,4 @@ function AddModal(props) {
 export default connect(({ report, loading }) => ({
   data: report.projectUser,
   loading: loading.models.report,
-}))(Form.create()(AddModal));
+}))(UserProjectRptModal);

+ 2 - 2
src/pages/PurchaseAdmin/PurchaseList/Report/UserRptModal.js

@@ -3,7 +3,7 @@ import { Modal, Table } from 'antd';
 import { connect } from 'dva';
 import moment from 'moment';
 
-function AddModal(props) {
+function UserRptModal(props) {
   const { dispatch, visible, onOk, onCancel, data, filter, loading } = props;
   const columns = [
     {
@@ -94,4 +94,4 @@ function AddModal(props) {
 export default connect(({ report, loading }) => ({
   data: report.depUser,
   loading: loading.models.report,
-}))(AddModal);
+}))(UserRptModal);

+ 149 - 54
src/pages/PurchaseAdmin/PurchaseList/Report/models/report.js

@@ -6,11 +6,15 @@ import {
   queryProjectReportDetail,
   queryUserProjectReport,
   queryProject,
+  queryDepCompare,
+  queryDepCompareUser,
+  queryFinanceReport,
+  queryUserProject,
 } from '@/services/workHours';
 import { queryRole } from '@/services/SysAdmin';
 import { message } from 'antd';
 import moment from 'moment';
-import { filter } from 'lodash';
+import { filter, times } from 'lodash';
 import list from '../../List/models/list';
 
 export default {
@@ -32,10 +36,17 @@ export default {
       list: [],
       pagination: {},
     },
-    project: {
+    // project: {
+    //   list: [],
+    //   pagination: {},
+    // },
+    project: [],
+    finance: {
       list: [],
       pagination: {},
     },
+    depCompare: [],
+    depUserProject: [],
     projectList: [],
   },
 
@@ -63,18 +74,18 @@ export default {
     },
     *queryUserProjectReport({ payload }, { call, put }) {
       const { data } = yield call(queryUserProjectReport, payload);
-      try {
-        data.list.forEach(item => {
-          let month = {};
-          item.month_rpt.forEach(mItem => {
-            let m = moment(mItem.ts).month();
-            month[m] = mItem;
-          });
-          item.month = month;
-        });
-      } catch (error) {
-        console.error(error);
-      }
+      // try {
+      //   data.list.forEach(item => {
+      //     let month = {};
+      //     item.month_rpt.forEach(mItem => {
+      //       let m = moment(mItem.ts).month();
+      //       month[m] = mItem;
+      //     });
+      //     item.month = month;
+      //   });
+      // } catch (error) {
+      //   console.error(error);
+      // }
       yield put({
         type: 'save',
         payload: { projectUser: data },
@@ -84,7 +95,7 @@ export default {
       const { filter = {}, record = {} } = payload;
       const { data } = yield call(queryDepReport, { ...filter, dep_id: record.dep_id });
       data.list.forEach(item => {
-        if(item.sub_dep_num) item.children = [];
+        if (item.sub_dep_num) item.children = [];
         item.isLoad = false;
       });
       // 判断是否为根节点
@@ -94,7 +105,7 @@ export default {
         record.isLoad = true;
         yield put({
           type: 'save',
-          payload: { dep: { ...dep } },
+          payload: { dep: { ...dep, list: [...dep.list] } },
         });
       } else {
         yield put({
@@ -106,55 +117,88 @@ export default {
     *queryProjectReport({ payload }, { call, put }) {
       const { data } = yield call(queryProjectReport, {
         ...payload,
-        // pageSize: 20,
       });
-      let treeData = {};
-      try {
-        data.list.forEach(item => {
-          let p_type_id = item.project_id == 0 ? 't' + item.p_type_id : 'p' + item.project_id;
-          treeData[p_type_id] = {
-            id: p_type_id,
-            type_name:
-              item.project_id == 0
-                ? item.project_name
-                : `${item.project_name}(${item.project_code})`,
-            project_name: item.project_name,
-            project_id: item.project_id,
-            project_code: item.project_code,
-            p_type_id: item.p_type_id,
-            month_rpt: item.month_rpt,
-            type_code: '',
-            children: [],
-            month: {},
-            isLoad: false,
-            isParent: true,
-          };
-          item.month_rpt.forEach(mItem => {
-            // let m = moment(mItem.ts).month();
-            treeData[p_type_id].month[mItem.ts] = mItem;
-          });
-        });
-      } catch (error) {
-        console.log(error);
-      }
-      console.log(treeData);
+      const idHelper = (item, params) => {
+        if (params.flag === undefined) {
+          item.child.forEach(childItem => idHelper(childItem, { flag: item.id }));
+          item.key = 'f' + item.id;
+        } else {
+          item.flag = params.flag;
+          if (params.project_id === undefined) {
+            item.child?.forEach(childItem =>
+              idHelper(childItem, { flag: item.flag, project_id: item.id })
+            );
+            item.key = 'f' + item.flag + 'p' + item.id;
+          } else {
+            item.project_id = params.project_id;
+            item.key = 'f' + item.flag + 'p' + item.project_id + 't' + item.id;
+          }
+        }
+      };
+      data.forEach(item => idHelper(item, {}));
       yield put({
         type: 'save',
         payload: {
-          project: {
-            list: Object.values(treeData),
-            pagination: data.pagination,
-          },
+          project: data,
         },
       });
     },
+    // *queryProjectReport({ payload }, { call, put }) {
+    //   const { data } = yield call(queryProjectReport, {
+    //     ...payload,
+    //     // pageSize: 20,
+    //   });
+    //   let treeData = {};
+    //   try {
+    //     data.list.forEach(item => {
+    //       let p_type_id =
+    //         item.rpt.project_id == 0 ? 't' + item.rpt.p_type_id : 'p' + item.rpt.project_id;
+    //       treeData[p_type_id] = {
+    //         id: p_type_id,
+    //         type_name:
+    //           item.rpt.project_id == 0
+    //             ? item.rpt.project_name
+    //             : `${item.rpt.project_name}(${item.rpt.project_code})`,
+    //         project_name: item.rpt.project_name,
+    //         project_id: item.rpt.project_id,
+    //         project_code: item.rpt.project_code,
+    //         p_type_id: item.rpt.p_type_id,
+    //         month_rpt: item.rpt.month_rpt,
+    //         classification: item.classification,
+    //         project_ids: item.project_ids,
+    //         type_code: '',
+    //         children: [],
+    //         month: {},
+    //         isLoad: false,
+    //         isParent: true,
+    //       };
+    //       item.rpt.month_rpt.forEach(mItem => {
+    //         // let m = moment(mItem.ts).month();
+    //         treeData[p_type_id].month[mItem.ts] = mItem;
+    //       });
+    //     });
+    //   } catch (error) {
+    //     console.log(error);
+    //   }
+    //   console.log(treeData);
+    //   yield put({
+    //     type: 'save',
+    //     payload: {
+    //       project: {
+    //         list: Object.values(treeData),
+    //         pagination: data.pagination,
+    //       },
+    //     },
+    //   });
+    // },
     *queryProjectReportDetail({ payload = {} }, { call, put, select }) {
       const { record = {} } = payload;
       const { data } = yield call(queryProjectReportDetail, {
         s_time: payload.s_time,
         e_time: payload.e_time,
-        project_id: record.project_id,
+        classification: record.classification,
         p_type_id: record.p_type_id,
+        project_ids: record.project_ids.join(','),
       });
       data.forEach((item, index) => {
         let month = {};
@@ -162,18 +206,69 @@ export default {
           // let m = moment(mItem.ts).month();
           month[mItem.ts] = mItem;
         });
-      
+
         item.month = month;
         item.id = record.id + '_' + index;
       });
       const project = yield select(s => s.report.project);
-      record.children = data.length == 0 ? null : data;
+      record.children =
+        data.length == 0 ? null : data.map(item => ({ ...item, project_ids: record.project_ids }));
       record.isLoad = true;
       yield put({
         type: 'save',
         payload: { project: { ...project } },
       });
     },
+    *queryFinanceReport({ payload = {} }, { call, put }) {
+      const { data } = yield call(queryFinanceReport, payload);
+      yield put({
+        type: 'save',
+        payload: { finance: data },
+      });
+    },
+    *queryDepCompare({ payload }, { call, put }) {
+      let { data } = yield call(queryDepCompare, payload);
+      if (data) {
+        const treeHelper = item => {
+          item.key = 'd' + item.dep_id;
+          if (!item.child && item.total_cnt) item.child = [];
+          item.child && item.child.forEach(childItem => treeHelper(childItem));
+        };
+        data.forEach(item => treeHelper(item));
+      } else data = [];
+      yield put({
+        type: 'save',
+        payload: { depCompare: data },
+      });
+    },
+    *queryDepCompareUser({ payload }, { call, put, select }) {
+      try {
+        let record = payload.record;
+        payload.dep_id = record.dep_id;
+        delete payload.record;
+        let { data } = yield call(queryDepCompareUser, payload);
+        data.forEach(item => {
+          item.key = 'u' + item.user_id;
+          item.dep_id = payload.dep_id;
+        });
+        record.child = [...record.child, ...data];
+        record.isLoad = true;
+      } catch (error) {
+        console.log(error);
+      }
+      const depCompare = yield select(s => s.report.depCompare);
+      yield put({
+        type: 'save',
+        payload: { depCompare: [...depCompare] },
+      });
+    },
+    *queryDepUserProject({ payload }, { call, put }) {
+      const { data } = yield call(queryUserProject, payload);
+      yield put({
+        type: 'save',
+        payload: { depUserProject: data },
+      });
+    },
   },
 
   reducers: {

+ 101 - 94
src/pages/PurchaseAdmin/PurchaseList/WorkingHours/AddModal.js

@@ -1,19 +1,28 @@
 import React, { useState } from 'react';
-import { Form } from '@ant-design/compatible';
-import '@ant-design/compatible/assets/index.css';
-import { message, Modal, Select, DatePicker } from 'antd';
+import { message, Form, Modal, Select, DatePicker } from 'antd';
+import { connect } from 'dva';
 import moment from 'moment';
 
 const { Option } = Select;
 const { MonthPicker } = DatePicker;
 
 function AddModal(props) {
-  const { visible, onOk, onCancel, time, form, typeList = [], projectList = {}, loading } = props;
+  const {
+    visible,
+    onOk,
+    onCancel,
+    time,
+    typeList = [],
+    subTypeList = [],
+    dispatch,
+    project = [],
+    loading,
+  } = props;
+  const [form] = Form.useForm();
   const [type, setType] = useState({});
   const [subType, setSubType] = useState({});
   const handleOk = () => {
-    form.validateFields((error, values) => {
-      if (error) return;
+    form.validateFields().then(values => {
       if (type.type == 0 || subType.type == 0) {
         if (!values.project) {
           message.error('请选择项目');
@@ -50,56 +59,75 @@ function AddModal(props) {
   };
   const onChangeType = value => {
     let item = typeList.find(t => t.id == value);
-    if (value == 35) {
-      form.setFieldsValue({
-        project: null,
-        subType: item.children[0].id + '',
+    form.setFieldsValue({
+      project: null,
+      subType: null,
+    });
+    setType(item);
+    setSubType({});
+    dispatch({
+      type: 'workload/save',
+      payload: { subTypeList: [] },
+    });
+    if (!item.type) {
+      dispatch({
+        type: 'workload/queryProject',
+        payload: { stage: value },
       });
-      setSubType(item.children[0]);
-      setType(item);
     } else {
-      form.setFieldsValue({
-        project: null,
-        subType: null,
+      dispatch({
+        type: 'workload/querySubType',
+        payload: { parent_id: item.id },
+        callback: subTypeList => {
+          if (value == 33) {
+            form.setFieldsValue({
+              project: null,
+              subType: subTypeList[0].id + '',
+            });
+            setSubType(subTypeList[0]);
+          }
+        },
       });
-      setSubType({});
-      setType(item);
     }
   };
   const onChangeSubType = value => {
-    let item = type.children.find(t => t.id == value);
+    let item = subTypeList.find(t => t.id == value);
     setSubType(item);
   };
 
   //售前支持更改项目时与子类选单联动
   const onChangeProject = value => {
-    if (value == '0') {
-      form.setFieldsValue({ subType: type.children[0].id + '' });
-      setSubType(type.children[0]);
-    } else {
-      form.setFieldsValue({ subType: type.children[1].id + '' });
-      setSubType(type.children[1]);
-    }
+    form.setFieldsValue({ subType: '' });
+    dispatch({
+      type: 'workload/querySubType',
+      payload: { parent_id: type.id, project_id: value },
+      callback: subTypeList => {
+        if (type.id == 35) {
+          form.setFieldsValue({ subType: subTypeList[0].id + '' });
+          setSubType(subTypeList[0]);
+        }
+        if (type.id == 2) {
+          if (value == '0') {
+            form.setFieldsValue({ subType: subTypeList[0].id + '' });
+            setSubType(subTypeList[0]);
+          } else {
+            form.setFieldsValue({ subType: subTypeList[1].id + '' });
+            setSubType(subTypeList[1]);
+          }
+        }
+      },
+    });
   };
 
   //分类选单
   const renderType = () => {
     return (
-      <Form.Item label="分类">
-        {form.getFieldDecorator('type', {
-          rules: [
-            {
-              required: true,
-              message: '请选择分类',
-            },
-          ],
-        })(
-          <Select onChange={onChangeType} placeholder="请选择分类">
-            {typeList.map(item => (
-              <Option key={String(item.id)}>{item.name}</Option>
-            ))}
-          </Select>
-        )}
+      <Form.Item label="分类" name="type" rules={[{ required: true, message: '请选择分类' }]}>
+        <Select onChange={onChangeType} placeholder="请选择分类">
+          {typeList.map(item => (
+            <Option key={String(item.id)}>{item.name}</Option>
+          ))}
+        </Select>
       </Form.Item>
     );
   };
@@ -107,37 +135,22 @@ function AddModal(props) {
   //项目选单
   const renderProject = () => {
     return (
-      <Form.Item label="项目">
-        {form.getFieldDecorator('project', {
-          rules: [
-            {
-              required: true,
-              message: '请选择项目',
-            },
-          ],
-        })(
-          //售前支持特殊判断,增加“无项目”,选择项目时与子类联动
-          <Select
-            showSearch
-            onChange={type.id === 2 ? onChangeProject : null}
-            placeholder="请选择项目"
-            optionFilterProp="children"
-            filterOption={(input, option) =>
-              // console.log(option.props.children)
-              option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
-            }
-          >
-            {type.id === 2 && <Option key={'0'}>售前支持(10100)</Option>}
-            {(type.id === 2
-              ? projectList.project0
-              : type.id === 3
-              ? projectList.project1
-              : projectList.project7
-            ).map(item => (
-              <Option key={String(item.ID)}>{`${item.Name}(${item.Code})`}</Option>
-            ))}
-          </Select>
-        )}
+      <Form.Item label="项目" name="project" rules={[{ required: true, message: '请选择项目' }]}>
+        {/* 售前支持特殊判断,增加“无项目”,选择项目时与子类联动 */}
+        <Select
+          showSearch
+          onChange={onChangeProject}
+          placeholder="请选择项目"
+          optionFilterProp="children"
+          filterOption={(input, option) =>
+            option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+          }
+        >
+          {type.id === 2 && <Option key={'0'}>售前支持(10100)</Option>}
+          {project.map(item => (
+            <Option key={String(item.ID)}>{`${item.Name}(${item.Code})`}</Option>
+          ))}
+        </Select>
       </Form.Item>
     );
   };
@@ -145,26 +158,17 @@ function AddModal(props) {
   //子类选单
   const renderSubType = () => {
     return (
-      <Form.Item label="子类">
-        {form.getFieldDecorator('subType', {
-          rules: [
-            {
-              required: true,
-              message: '请选择子类',
-            },
-          ],
-        })(
-          //售前支持特殊判断,子类不可选
-          <Select
-            disabled={type.id === 2 || type.id === 35}
-            onChange={onChangeSubType}
-            placeholder={type.id === 2 || type.id === 35 ? null : '请选择子类'}
-          >
-            {(type.children || []).map(item => (
-              <Option key={String(item.id)}>{`${item.name}(${item.code})`}</Option>
-            ))}
-          </Select>
-        )}
+      <Form.Item label="子类" name="subType" rules={[{ required: true, message: '请选择子类' }]}>
+        {/* 售前支持特殊判断,子类不可选 */}
+        <Select
+          disabled={type.id === 2}
+          onChange={onChangeSubType}
+          placeholder={type.id === 2 ? null : '请选择子类'}
+        >
+          {(subTypeList || []).map(item => (
+            <Option key={String(item.id)}>{`${item.name}(${item.code})`}</Option>
+          ))}
+        </Select>
       </Form.Item>
     );
   };
@@ -179,9 +183,9 @@ function AddModal(props) {
       onOk={handleOk}
       destroyOnClose
     >
-      <Form labelCol={{ span: 4 }} wrapperCol={{ span: 16 }}>
+      <Form labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} form={form}>
         {renderType()}
-        {(type.id === 2 || type.id === 3 || type.id === 35) && renderProject()}
+        {type.type == 0 && renderProject()}
         {renderSubType()}
         <Form.Item label="时间">{time}</Form.Item>
       </Form>
@@ -189,4 +193,7 @@ function AddModal(props) {
   );
 }
 
-export default Form.create()(AddModal);
+export default connect(({ workload }) => ({
+  project: workload.project,
+  subTypeList: workload.subTypeList,
+}))(AddModal);

+ 59 - 14
src/pages/PurchaseAdmin/PurchaseList/WorkingHours/Auth.js

@@ -1,6 +1,4 @@
 import React, { useState, useEffect, useRef } from 'react';
-import { Form } from '@ant-design/compatible';
-import '@ant-design/compatible/assets/index.css';
 import {
   message,
   InputNumber,
@@ -20,10 +18,9 @@ import AuthWorkList from './AuthWorkList';
 import { connect } from 'dva';
 import styles from './index.less';
 import moment from 'moment';
-import { getList } from '@/services/devOpsScoreRule';
 
 function List(props) {
-  const { typeList, dispatch, loading, form, list, project, currentUser, allType, user } = props;
+  const { dispatch, loading, list, project, currentUser, allType, user } = props;
   const [visible, setVisible] = useState(false);
   const [filter, setFilter] = useState({});
   const [current, setCurrent] = useState({
@@ -45,9 +42,16 @@ function List(props) {
           status: 2,
           desc: '',
         }));
+        let time = current.date.format('YYYY-MM-DD');
         dispatch({
           type: 'workload/authWorkload',
           payload: params,
+          callback: list => {
+            setCurrent({
+              ...current,
+              list: list.filter(item => item.time == time),
+            });
+          },
         });
       },
     });
@@ -62,11 +66,16 @@ function List(props) {
       status: 3,
       desc: data.desc || '',
     }));
+    let time = current.date.format('YYYY-MM-DD');
     dispatch({
       type: 'workload/authWorkload',
       payload: params,
-      callback: () => {
+      callback: list => {
         setVisible(false);
+        setCurrent({
+          ...current,
+          list: list.filter(item => item.time == time),
+        });
       },
     });
   };
@@ -84,10 +93,32 @@ function List(props) {
 
   const onChangeDate = value => {
     let time = value.format('YYYY-MM-DD');
-    setCurrent({
-      date: value,
-      list: list.filter(item => item.time == time),
-    });
+    if (current.date.format('YYYY-MM') != value.format('YYYY-MM')) {
+      const s_date = value.format('YYYY-MM') + '-01';
+      const e_date = moment(s_date)
+        .add('month', 1)
+        .add('days', -1)
+        .format('YYYY-MM-DD');
+      dispatch({
+        type: 'workload/queryAuthWorkHours',
+        payload: {
+          pageSize: 9999,
+          s_time: s_date + ' 00:00:00',
+          e_time: e_date + ' 23:59:59',
+        },
+        callback: list => {
+          setCurrent({
+            date: value,
+            list: list.filter(item => item.time == time),
+          });
+        },
+      });
+    } else {
+      setCurrent({
+        date: value,
+        list: list.filter(item => item.time == time),
+      });
+    }
   };
 
   const getList = () => {
@@ -105,14 +136,29 @@ function List(props) {
     dispatch({
       type: 'workload/queryWorkType',
       callback: () => {
+        const s_date = current.date.format('YYYY-MM') + '-01';
+        const e_date = moment(s_date)
+          .add('month', 1)
+          .add('days', -1)
+          .format('YYYY-MM-DD');
         dispatch({
           type: 'workload/queryAuthWorkHours',
           payload: {
             pageSize: 9999,
+            s_time: s_date + ' 00:00:00',
+            e_time: e_date + ' 23:59:59',
+          },
+          callback: list => {
+            let time = current.date.format('YYYY-MM-DD');
+            setCurrent({
+              ...current,
+              list: list.filter(item => item.time == time),
+            });
           },
         });
       },
     });
+
     // 查询项目列表
     dispatch({
       type: 'workload/queryProject',
@@ -132,9 +178,9 @@ function List(props) {
     };
   }, [currentUser.ID]);
 
-  useEffect(() => {
-    onChangeDate(current.date);
-  }, [list]);
+  // useEffect(() => {
+  //   onChangeDate(current.date);
+  // }, [list]);
 
   return (
     <div>
@@ -170,10 +216,9 @@ function List(props) {
 
 export default connect(({ workload, user, loading }) => ({
   list: workload.list,
-  typeList: workload.typeList,
   user: user.list,
   allType: workload.allType,
   project: workload.project,
   currentUser: user.currentUser,
   loading: loading.models.workload,
-}))(Form.create()(List));
+}))(List);

+ 32 - 52
src/pages/PurchaseAdmin/PurchaseList/WorkingHours/AuthWorkList.js

@@ -1,7 +1,6 @@
 import React, { useState } from 'react';
-import { Form } from '@ant-design/compatible';
-import '@ant-design/compatible/assets/index.css';
 import {
+  Form,
   InputNumber,
   Input,
   Button,
@@ -20,18 +19,8 @@ const { Panel } = Collapse;
 const { Option } = Select;
 
 function AuthWorkList(props) {
-  const {
-    list,
-    onAuth,
-    onSearch,
-    onSave,
-    project = [],
-    allType,
-    onAgree,
-    onReject,
-    user,
-    form,
-  } = props;
+  const { list, onAuth, onSearch, onSave, project = [], allType, onAgree, onReject, user } = props;
+  const [form] = Form.useForm();
   const [edit, setEdit] = useState(false);
   const columns = [
     {
@@ -41,11 +30,7 @@ function AuthWorkList(props) {
     },
     {
       title: '提交人',
-      dataIndex: 'User.CName',
-      // render: (project_id, item) => {
-      //   if (item.zIndex === 0) return '';
-      //   return project.find(p => p.ID == project_id)?.Name || '-';
-      // },
+      dataIndex: ['User', 'CName'],
     },
     {
       title: '审核状态',
@@ -107,7 +92,7 @@ function AuthWorkList(props) {
       if (id == '0') {
         name = '其他';
       } else {
-        name = project.find(p => p.ID == id)?.Name;
+        name = item.Project.Name;
       }
 
       if (!data[id]) data[id] = { list: [], name, id };
@@ -155,46 +140,41 @@ function AuthWorkList(props) {
 
   const renderSearch = () => {
     const onHandleSearch = () => {
-      form.validateFields((error, values) => {
-        if (error) return;
+      form.validateFields().then(values => {
         onSearch(values);
       });
     };
     return (
-      <Form style={{ marginBottom: 20 }}>
+      <Form style={{ marginBottom: 20 }} form={form}>
         <Row gutter={12}>
           <Col span={12}>
-            <Form.Item label="人员">
-              {form.getFieldDecorator('userId')(
-                <Select
-                  allowClear
-                  showSearch
-                  filterOption={(input, option) =>
-                    option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
-                  }
-                >
-                  {user.map(item => (
-                    <Option key={item.ID}>{item.CName}</Option>
-                  ))}
-                </Select>
-              )}
+            <Form.Item label="人员" name="userId">
+              <Select
+                allowClear
+                showSearch
+                filterOption={(input, option) =>
+                  option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                }
+              >
+                {user.map(item => (
+                  <Option key={item.ID}>{item.CName}</Option>
+                ))}
+              </Select>
             </Form.Item>
           </Col>
           <Col span={12}>
-            <Form.Item label="项目">
-              {form.getFieldDecorator('projectId')(
-                <Select
-                  allowClear
-                  showSearch
-                  filterOption={(input, option) =>
-                    option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
-                  }
-                >
-                  {project.map(item => (
-                    <Option key={item.ID}>{item.Name}</Option>
-                  ))}
-                </Select>
-              )}
+            <Form.Item label="项目" name="projectId">
+              <Select
+                allowClear
+                showSearch
+                filterOption={(input, option) =>
+                  option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                }
+              >
+                {project.map(item => (
+                  <Option key={item.ID}>{`${item.Name}(${item.Code})`}</Option>
+                ))}
+              </Select>
             </Form.Item>
           </Col>
         </Row>
@@ -243,4 +223,4 @@ function AuthWorkList(props) {
   );
 }
 
-export default Form.create()(AuthWorkList);
+export default AuthWorkList;

+ 13 - 26
src/pages/PurchaseAdmin/PurchaseList/WorkingHours/CalendarModal.js

@@ -1,12 +1,11 @@
 import React, { useState, useEffect } from 'react';
-import { Form } from '@ant-design/compatible';
-import '@ant-design/compatible/assets/index.css';
 import {
   InputNumber,
   message,
   Modal,
   Calendar,
   Popover,
+  Form,
   Input,
   Row,
   Col,
@@ -17,18 +16,8 @@ import styles from './index.less';
 import moment from 'moment';
 
 function CalendarModal(props) {
-  const {
-    allType,
-    projectList,
-    visible,
-    onOk,
-    onCancel,
-    data,
-    title,
-    form,
-    footer,
-    loading,
-  } = props;
+  const { allType, projectList, visible, onOk, onCancel, data, title, footer, loading } = props;
+  const [form] = Form.useForm();
   const [type, setType] = useState({});
   const [edit, setEdit] = useState(false);
   const [validRange, setValidRange] = useState(null);
@@ -37,9 +26,7 @@ function CalendarModal(props) {
     list: [],
   });
   const handleOk = () => {
-    form.validateFields((error, values) => {
-      if (error) return;
-      console.log(values);
+    form.validateFields().then(values => {
       let params = [];
       Object.keys(values).forEach(type_id => {
         if (type_id == 'comment') return;
@@ -147,16 +134,16 @@ function CalendarModal(props) {
         <Col span={6}>
           <Button>添加</Button>
           {current.list.map(item => (
-            <Form.Item label={item.TypeInfo?.name}>
-              {form.getFieldDecorator(`${item.type_id}.${item.time}`, {
-                initialValue: item?.workload,
-              })(<InputNumber min={0} max={8} disabled={!edit} />)}
+            <Form.Item
+              label={item.TypeInfo?.name}
+              name={`${item.type_id}.${item.time}`}
+              initialValue={item?.workload}
+            >
+              <InputNumber min={0} max={8} disabled={!edit} />
             </Form.Item>
           ))}
-          <Form.Item label="日志">
-            {form.getFieldDecorator('comment', { initialValue: current.list[0]?.comment })(
-              <Input.TextArea rows={5} disabled={!edit} />
-            )}
+          <Form.Item label="日志" name="comment" initialValue={current.list[0]?.comment}>
+            <Input.TextArea rows={5} disabled={!edit} />
           </Form.Item>
 
           <div className={styles.btns}>
@@ -186,4 +173,4 @@ function CalendarModal(props) {
   );
 }
 
-export default Form.create()(CalendarModal);
+export default CalendarModal;

+ 9 - 10
src/pages/PurchaseAdmin/PurchaseList/WorkingHours/RejectModal.js

@@ -1,15 +1,12 @@
 import React, { useState } from 'react';
-import { Form } from '@ant-design/compatible';
-import '@ant-design/compatible/assets/index.css';
-import { Input, message, Modal } from 'antd';
+import { Input, message, Form, Modal } from 'antd';
 
 function RejectModal(props) {
-  const { visible, onOk, onCancel, form, loading } = props;
+  const { visible, onOk, onCancel, loading } = props;
+  const [form] = Form.useForm();
   const [type, setType] = useState({});
   const handleOk = () => {
-    form.validateFields((error, values) => {
-      if (error) return;
-
+    form.validateFields().then(values => {
       onOk(values);
     });
   };
@@ -24,11 +21,13 @@ function RejectModal(props) {
       onOk={handleOk}
       destroyOnClose
     >
-      <Form labelCol={{ span: 4 }} wrapperCol={{ span: 16 }}>
-        <Form.Item label="拒绝理由">{form.getFieldDecorator('desc')(<Input.TextArea />)}</Form.Item>
+      <Form labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} form={form}>
+        <Form.Item label="拒绝理由" name="desc">
+          <Input.TextArea />
+        </Form.Item>
       </Form>
     </Modal>
   );
 }
 
-export default Form.create()(RejectModal);
+export default RejectModal;

+ 1 - 3
src/pages/PurchaseAdmin/PurchaseList/WorkingHours/SearchForm.js

@@ -1,7 +1,5 @@
 import React from 'react';
-import { Form } from '@ant-design/compatible';
-import '@ant-design/compatible/assets/index.css';
-import { Select, DatePicker, Button } from 'antd';
+import { Form, Select, DatePicker, Button } from 'antd';
 import moment from 'moment';
 
 const { Option } = Select;

+ 30 - 16
src/pages/PurchaseAdmin/PurchaseList/WorkingHours/WorkList.js

@@ -1,60 +1,74 @@
 import React, { useState, useEffect } from 'react';
-import { Form } from '@ant-design/compatible';
-import '@ant-design/compatible/assets/index.css';
 import { InputNumber, Popover, Divider, Table, message } from 'antd';
 
 function WorkList(props) {
-  const { list, form, onAuth, onSave, project = [], allType } = props;
+  const { list, onAuth, onSave, allType } = props;
   const [expandedRowKeys, setExpandedRowKeys] = useState([]);
   const [dataSource, setDataSource] = useState([]);
+  let workHour = {};
   const columns = [
     {
       title: '分类',
       dataIndex: 'type_id',
-      width: 230,
+      width: '25%',
       render: type_id => allType[type_id]?.name,
     },
     {
       title: '所属项目',
-      dataIndex: 'project_id',
-      render: (project_id, item) => {
+      width: '30%',
+      render: item => {
         if (item.zIndex === 0) return '';
-        return project.find(p => p.ID == project_id)?.Name || '-';
+        return item.Project?.Name || '-';
       },
     },
     {
       title: '审核状态',
-      width: 100,
+      width: '15%',
       render: record => renderStatus(record),
     },
     {
       title: '工时',
-      width: 110,
+      width: '15%',
       render: item => {
         if (item.zIndex === 0) {
           return '总计 ' + item.children.reduce((total, item) => total + item.workload, 0) + '小时';
         }
         if (item.audit_state == 1 || item.audit_state == 2) return item?.workload + '小时';
-        return form.getFieldDecorator(`${item.type_id}.${item.time}`, {
-          initialValue: item?.workload,
-        })(<InputNumber style={{width: 60}} min={0} step={0.5} />);
+        workHour[`${item.type_id}.${item.project_id}.${item.time}`] = item?.workload;
+        return (
+          <InputNumber
+            style={{ width: 60 }}
+            min={0}
+            step={0.5}
+            defaultValue={item?.workload}
+            onChange={value => {
+              workHour[`${item.type_id}.${item.project_id}.${item.time}`] = value;
+            }}
+          />
+        );
       },
     },
     {
       title: '操作',
-      width: 140,
+      width: '15%',
       render: item => {
         if (item.zIndex === 0) return '';
         if (item.audit_state == 1 || item.audit_state == 2) return;
         return (
           <>
             <a
-              onClick={() => onHandleSave(item, form.getFieldValue(`${item.type_id}.${item.time}`))}
+              onClick={() =>
+                onHandleSave(item, workHour[`${item.type_id}.${item.project_id}.${item.time}`])
+              }
             >
               保存
             </a>
             <Divider type="vertical"></Divider>
-            <a onClick={() => onAuth(item, form.getFieldValue(`${item.type_id}.${item.time}`))}>
+            <a
+              onClick={() =>
+                onAuth(item, workHour[`${item.type_id}.${item.project_id}.${item.time}`])
+              }
+            >
               上报审批
             </a>
           </>
@@ -128,4 +142,4 @@ function WorkList(props) {
   );
 }
 
-export default Form.create()(WorkList);
+export default WorkList;

+ 18 - 32
src/pages/PurchaseAdmin/PurchaseList/WorkingHours/index.js

@@ -1,24 +1,12 @@
 import React, { useState, useEffect, useRef } from 'react';
-import { Form } from '@ant-design/compatible';
-import '@ant-design/compatible/assets/index.css';
-import { Modal, Button, Calendar, Popover, Spin, Row, Col } from 'antd';
+import { Modal, Button, Calendar, Popover, Spin, Row, Col, message } from 'antd';
 import AddModal from './AddModal';
 import WorkList from './WorkList';
 import { connect } from 'dva';
 import moment from 'moment';
 
 function List(props) {
-  const {
-    typeList,
-    dispatch,
-    loading,
-    form,
-    dataList,
-    project,
-    projectList,
-    currentUser,
-    allType,
-  } = props;
+  const { typeList, dispatch, loading, dataList, projectList, currentUser, allType } = props;
   const [visible, setVisible] = useState(false);
   const [current, setCurrent] = useState({
     date: moment(),
@@ -26,7 +14,7 @@ function List(props) {
   });
 
   const onAuth = (item, workload) => {
-    console.log(workload);
+    if (workload == 0) return message.error('请上报有效工时');
     Modal.confirm({
       title: '提示',
       content: '是否上报审批?',
@@ -178,6 +166,13 @@ function List(props) {
     dispatch({
       type: 'workload/addWorkHours',
       payload: params,
+      callback: list => {
+        let time = current.date.format('YYYY-MM-DD');
+        setCurrent({
+          ...current,
+          list: list.filter(item => item.time == time),
+        });
+      },
     });
   };
 
@@ -265,7 +260,7 @@ function List(props) {
       if (!pid) return '';
       name = allType[pid].name;
     } else {
-      name = project?.find(p => p.ID == item.project_id)?.Name;
+      name = item.Project.Name;
     }
     return name;
   };
@@ -300,9 +295,9 @@ function List(props) {
       },
     });
     // 查询项目列表
-    dispatch({
-      type: 'workload/queryProject',
-    });
+    // dispatch({
+    //   type: 'workload/queryProject',
+    // });
     return () => {
       // 清空查询数据
       dispatch({
@@ -323,14 +318,14 @@ function List(props) {
     <div>
       <Spin spinning={loading}>
         <Row gutter={8}>
-          <Col span={12}>
+          <Col span={10}>
             <Calendar
               value={current.date}
               dateCellRender={dateCellRender}
               onChange={onChangeDate}
             />
           </Col>
-          <Col span={12}>
+          <Col span={14}>
             <div>
               <Button type="primary" style={{ marginBottom: 20 }} onClick={() => setVisible(true)}>
                 新增
@@ -353,20 +348,13 @@ function List(props) {
               )}
             </div>
 
-            <WorkList
-              allType={allType}
-              project={project}
-              list={current.list}
-              onAuth={onAuth}
-              onSave={onSave}
-            />
+            <WorkList allType={allType} list={current.list} onAuth={onAuth} onSave={onSave} />
           </Col>
         </Row>
       </Spin>
 
       <AddModal
         typeList={typeList}
-        projectList={projectList}
         visible={visible}
         onOk={onAddWork}
         time={current.date?.format('YYYY-MM-DD')}
@@ -380,8 +368,6 @@ export default connect(({ workload, user, loading }) => ({
   dataList: workload.dataList,
   typeList: workload.typeList,
   allType: workload.allType,
-  project: workload.project,
-  projectList: workload.projectList,
   currentUser: user.currentUser,
   loading: loading.models.workload,
-}))(Form.create()(List));
+}))(List);

+ 26 - 21
src/pages/PurchaseAdmin/PurchaseList/WorkingHours/models/workingHours.js

@@ -18,35 +18,43 @@ export default {
     dataList: [],
     typeList: [],
     allType: {},
+    subTypeList: [],
     project: [],
     projectList: { project0: [], project1: [] },
     filter: {},
   },
 
   effects: {
-    *queryWorkType({ payload, callback }, { call, put }) {
-      const { data } = yield call(queryWorkType, {});
+    *queryWorkType({ callback }, { call, put }) {
+      const { data } = yield call(queryWorkType, { parent_id: -1 });
+      let typeList = [];
       let allType = {};
       for (let i = 0; i < data.length; i++) {
         let item = data[i];
         allType[item.id] = item;
-        let { data: subData } = yield call(queryWorkType, { parent_id: item.id });
-        item.children = subData;
-        subData.forEach(item => {
-          allType[item.id] = item;
-        });
+        if (item.parent_id == 0) {
+          typeList.push(item);
+        }
       }
       yield put({
         type: 'save',
-        payload: { typeList: data, allType },
+        payload: { typeList, allType },
       });
       callback && callback();
     },
+    *querySubType({ payload, callback }, { call, put }) {
+      const { data } = yield call(queryWorkType, payload);
+      callback && callback(data);
+      yield put({
+        type: 'save',
+        payload: { subTypeList: data },
+      });
+    },
     /**
      *
      * payload = {s_time,e_time,user_id}
      */
-    *queryAuthWorkHours({ payload }, { call, put, select }) {
+    *queryAuthWorkHours({ payload, callback }, { call, put, select }) {
       const workload = yield select(s => s.workload);
       const { typeList, filter, allType } = workload;
       // 合并新旧过滤条件
@@ -80,6 +88,9 @@ export default {
       //     });
       //   });
       // });
+
+      callback && callback(data.list);
+
       yield put({
         type: 'save',
         payload: { list: data.list, filter: newFilter },
@@ -156,29 +167,23 @@ export default {
       const res = yield call(authWorkload, payload);
       if (res) {
         message.success('操作成功');
-        callback && callback();
         yield put({
           type: 'queryAuthWorkHours',
           payload: {},
+          callback,
         });
       }
     },
-    *queryProject({ callback }, { call, put }) {
-      const response0 = yield call(queryProject, { stage: 0 });
-      const response1 = yield call(queryProject, { stage: 1 });
-      const response7 = yield call(queryProject, { stage: 7 });
-      if (response0 && response1 && response7) {
+    *queryProject({ payload = {}, callback }, { call, put }) {
+      const res = yield call(queryProject, payload);
+      if (res) {
         yield put({
           type: 'save',
           payload: {
-            project: [...response0.data.list, ...response1.data.list, ...response7.data.list],
-            projectList: {
-              project0: response0.data.list,
-              project1: response1.data.list,
-              project7: response7.data.list,
-            },
+            project: res.data.list,
           },
         });
+        callback && callback();
       }
     },
   },

+ 20 - 17
src/pages/document.ejs

@@ -1,27 +1,30 @@
 <!DOCTYPE html>
 <html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta http-equiv="Pragma" content="no-cache" />
+    <meta http-equiv="Cache-Control" content="no-cache" />
+    <meta http-equiv="Expires" content="Sat, 01 Dec 2001 00:00:00 GMT" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <meta
+      name="viewport"
+      content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=yes"
+    />
+    <title>金科环境股份有限公司</title>
+    <link rel="icon" href="<%= context.config.publicPath %>favicon.png" type="image/x-icon" />
 
-<head>
-  <meta charset="UTF-8" />
-  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
-  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
-  <title>金科环境股份有限公司</title>
-  <link rel="icon" href="<%= context.config.publicPath %>favicon.png" type="image/x-icon" />
-
-  <!-- <script src="https://gw.alipayobjects.com/os/lib/react/16.13.1/umd/react.production.min.js"></script>
+    <!-- <script src="https://gw.alipayobjects.com/os/lib/react/16.13.1/umd/react.production.min.js"></script>
   <script src="https://gw.alipayobjects.com/os/lib/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
   <script src="https://cdn.bootcdn.net/ajax/libs/antd/3.26.19/antd.min.js"></script>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/antv-g2/3.5.19/g2.min.js"></script>
   <script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script> -->
-</head>
-
-<body>
-  <!--    <noscript>Sorry, we need js to run correctly!</noscript>-->
-
-  <div id="root"></div>
+  </head>
 
-</body>
-<script src="<%= context.config.publicPath %>rem.js"></script>
+  <body>
+    <!--    <noscript>Sorry, we need js to run correctly!</noscript>-->
 
-</html>
+    <div id="root"></div>
+  </body>
+  <script src="<%= context.config.publicPath %>rem.js"></script>
+</html>

+ 13 - 0
src/services/DataMeter.js

@@ -204,3 +204,16 @@ export async function getProjectList(params) {
   return request(`/api/v2/project?${stringify(params)}`)
 }
 
+//获取巡检结果
+export async function getAutoPatrol(params) {
+  return request(`/patrol/auto/data/${params.projectId}`)
+}
+
+export async function getBreakdownList(params) {
+  return request(`/breakdown-record/list/${params.projectId}?${stringify(params)}`);
+}
+
+export async function getPatrolRecord(params) {
+  return request(`/patrol/data/${params.projectId}?${stringify(params)}`);
+}
+

+ 103 - 2
src/services/ProjectAdmin.js

@@ -1,4 +1,5 @@
 import request from '@/utils/request';
+import moment from 'moment';
 import { stringify } from 'qs';
 
 export async function queryProjectV1(params) {
@@ -256,7 +257,7 @@ export async function getDeviceRealData(params) {
 export async function getDeviceRealDataByTime(params) {
   params.size = 999999;
   return request(`/jinke-cloud/db/device/history-data?${stringify(params)}`, {
-  // return request(`/jinke-cloud/device/history-data?${stringify(params)}`, {
+    // return request(`/jinke-cloud/device/history-data?${stringify(params)}`, {
     method: 'GET',
     // body: params,
   });
@@ -272,5 +273,105 @@ export async function queryScreenShort(data) {
 }
 
 export async function deleteScreenShort(data) {
-  return request(`/api/v1/project-file/${data.id}`,{method: 'DELETE'});
+  return request(`/api/v1/project-file/${data.id}`, { method: 'DELETE' });
+}
+
+const CACHE = {};
+// 查询图表对应的表单数据-最新数据
+export async function queryFormCurrentData({ projectId, formName, titles }) {
+  if (!CACHE[formName]) {
+    const resCell = await queryFormCell({ projectId, formName });
+    CACHE[formName] = resCell.data || [];
+  }
+  let cells = titles.map(t => {
+    let cell = CACHE[formName].find(item => item.title == t);
+    return cell;
+  });
+  const { data } = await request(`/runtime_form/chart/current/${projectId}/${formName}`);
+
+  return cells.map(item => ({
+    key: item.cell_key,
+    value: data[item.cell_key],
+    title: item.title,
+  }));
+}
+
+export async function queryFormCell({ projectId, formName }) {
+  const res = await request(`/api/v1/runtime_form/cell/to/chart/${projectId}/${formName}`);
+  return res;
+}
+
+// 查询图表对应的表单数据-历史数据
+export async function queryFormHistoryData({ projectId, formName, titles, sTime, eTime }) {
+  // 查询表单配置
+  if (!CACHE[formName]) {
+    const resCell = await queryFormCell({ projectId, formName });
+    CACHE[formName] = resCell.data || [];
+  }
+  // 获取key、time与title的对应关系
+  let titleKey = titles.map(t => {
+    let data = CACHE[formName].filter(item => item.title == t);
+    if (data[0].time) {
+      return {
+        title: t,
+        keys: data.map(item => {
+          return {
+            time: item.time,
+            key: item.cell_key,
+          };
+        }),
+      };
+    } else {
+      return {
+        title: t,
+        keys: data.map(item => item.cell_key),
+      };
+    }
+  });
+  // 查询数据
+  const { data } = await request(
+    `/runtime_form/chart/list/${projectId}/${formName}?sTime=${sTime}&eTime=${eTime}`
+  );
+
+  return titleKey.map(item => {
+    const { title, keys } = item;
+    let chartData = [];
+    // 从数据中获取key和value
+    keys.forEach(keyItem => {
+      // 判断是否含有time
+      if (keyItem instanceof Object) {
+        const { key, time } = keyItem;
+        data.forEach(dItem => {
+          let value = dItem[key];
+          // 含有time则替换时间  c_time结构2022-03-10T16:05:37+08:00
+          let t = dItem.c_time.split('T')[0] + ' ' + dItem[time];
+          chartData.push({
+            htime: new Date(t),
+            val: value || 0,
+          });
+        });
+      } else {
+        data.forEach(dItem => {
+          let value = dItem[keyItem];
+          // 没有time则使用c_time
+          let time = dItem.c_time;
+          chartData.push({
+            htime: new Date(time),
+            val: value || 0,
+          });
+        });
+      }
+    });
+
+    chartData
+      .sort((a, b) => a.htime - b.htime)
+      .forEach(item => {
+        item.htime = moment(item.htime).format('YYYY-MM-DD HH:mm:ss');
+      });
+
+    return {
+      name: title,
+      data: chartData,
+    };
+  });
 }

+ 16 - 0
src/services/approval.js

@@ -88,6 +88,22 @@ export async function startExecution(data) {
   });
 }
 
+//转运营
+export async function startOperate(data) {
+  return request(`/api/v2/project_code/to_opt`, {
+    method: 'POST',
+    body: data,
+  });
+}
+
+//转质保
+export async function startQuality(data) {
+  return request(`/api/v2/project_code/to_wty`, {
+    method: 'POST',
+    body: data,
+  });
+}
+
 //移除成员
 export async function deleteMember(data) {
   return request(`/api/v2/project_code/user/${data.project_code_id}/${data.user_id}`, {

+ 136 - 1
src/services/boom.js

@@ -1,4 +1,6 @@
+import { message } from 'antd';
 import request from '@/utils/request';
+import { async } from '@antv/x6/lib/registry/marker/async';
 import { stringify } from 'qs';
 
 /**
@@ -68,6 +70,12 @@ export async function submitNextNode(params) {
     body: params,
   });
 }
+export async function advanceSubmitNextNode(params) {
+  return request(`/api/v1/purchase/next/node/advance-submit`, {
+    method: 'POST',
+    body: params,
+  });
+}
 
 export async function queryDetail(params) {
   let response = await request(`/purchase/record?${stringify(params)}`);
@@ -90,6 +98,50 @@ export async function queryHistoryList(params) {
 export async function queryBoomFlowList(params) {
   return request(`/purchase/bom/flows?${stringify(params)}`);
 }
+//请求历史版本
+export async function queryVersionsTree(params) {
+  return request(`/api/v1/purchase/record/version/tree?${stringify(params)}`);
+}
+//查询业务节点的审核记录
+export async function queryAuditExcel(params) {
+  return request(`/api/v1/purchase/audit/excel?${stringify(params)}`);
+}
+//查询审批节点的审核记录
+export async function queryAuditRecord(params) {
+  return request(`/api/v1/purchase/audit/record?${stringify(params)}`);
+}
+//查询表单数据接口
+export async function queryDingSchema(params) {
+  return request(`/api/v1/purchase/bom/ding/schema?${stringify(params)}`);
+}
+export async function queryDingInstanceDetail(params) {
+  let res = await request(`/api/v1/purchase/bom/ding/instance-detail`, {
+    method: 'POST',
+    body: params,
+  });
+  if (res.data.errcode != 0) {
+    message.error(res.data.errmsg);
+    throw new Error(res.data.errmsg);
+  }
+  return res;
+}
+export async function queryDingInstanceExecute(params) {
+  let res = await request(`/api/v1/purchase/bom/ding/instance-execute`, {
+    method: 'POST',
+    body: params,
+  });
+  if (res.data.errcode != 0) {
+    message.error('审批失败,请联系管理员。');
+    throw new Error(res.data.errmsg);
+  }
+  return res;
+}
+export async function queryListParentByUser(params) {
+  return request(`/api/v1/purchase/bom/ding/department/list-parent-by-user`, {
+    method: 'POST',
+    body: params,
+  });
+}
 /**
  * 查看项目流程列表
  * project_id
@@ -200,7 +252,11 @@ export async function queryBoomFlowDetail(params) {
         port: item.target_port,
       },
     };
-    edge.attrs = attrs;
+    try {
+      edge.attrs = item.attr ? JSON.parse(item.attr) : attrs;
+    } catch (error) {
+      edge.attrs = attrs;
+    }
     return edge;
   });
   return {
@@ -260,3 +316,82 @@ export async function addAuditNode(data) {
 export async function queryOSSData() {
   return request(`/config/chart-template-img?destDir=public/bom`);
 }
+
+export async function queryRecordSheet(data) {
+  return request(`/purchase/record/sheet?${stringify(data)}`, {
+    method: 'POST',
+    body: data,
+  });
+}
+export async function queryDingTemplateList() {
+  return request(`/purchase/bom/ding/template/list`);
+}
+
+export async function queryDDdepList(data) {
+  let res = await request(`/api/v1/purchase/bom/ding/department-list`, {
+    method: 'POST',
+    body: data,
+  });
+  return res.data.result;
+}
+
+export async function queryDDProcessesForecast(data) {
+  let res = await request(`/api/v1/purchase/bom/ding/processes-forecast`, {
+    method: 'POST',
+    body: data,
+  });
+  if (res.data.message) {
+    // message.error(res.data.message);
+    throw new Error(res.data.message);
+  }
+  return res.data.result;
+}
+
+export async function uploadFile(data) {
+  let res = await request(`/api/v1/purchase/bom/ding/upload-file`, {
+    method: 'POST',
+    body: data,
+    headers: {
+      ContentType: 'application/x-www-form-urlencoded',
+    },
+  });
+  if (!res.data.dentry) {
+    message.error(res.data.errmsg);
+    throw new Error(res.data.errmsg);
+  }
+  return res.data;
+}
+
+export async function bindDDCode(userId, code) {
+  let res = await request(`/api/v1/purchase/bom/ding/set-ding-user-code?ucode=${userId}:${code}`, {
+    method: 'GET',
+  });
+
+  return res.data;
+}
+
+export async function saveAuditFlowInfo(data) {
+  return request(`/purchase/flow/info`, {
+    method: 'POST',
+    body: data,
+  });
+}
+
+//获取部门结构
+export async function queryDepV2(params) {
+  return request(`/api/v2/dep?${stringify(params)}`);
+}
+
+export async function queryProcessFlows(params) {
+  let res = await request(`/purchase/process/get-flows?${stringify(params)}`, {
+    method: 'GET',
+  });
+  return res.data;
+}
+
+export async function queryUserListByRoleID(params) {
+  let res = await request(`/api/v1/purchase/process/get-role-user?${stringify(params)}`, {
+    method: 'GET',
+  });
+  return res.data;
+}

+ 18 - 10
src/services/user.js

@@ -5,19 +5,22 @@ export async function query() {
 }
 
 export async function queryCurrent() {
-  return request('/user/current-user', {method: 'GET'});
+  return request('/user/current-user', { method: 'GET' });
 }
 
 export async function queryCurrentV2() {
-  return request('/api/v2/user/current-user', {method: 'GET'});
+  return request('/api/v2/user/current-user', { method: 'GET' });
+}
+export async function queryUserRole(userId) {
+  return request('/api/v2/user/detail/' + userId, { method: 'GET' });
 }
 
 export async function queryUnreadNotification() {
-  return request('/notification/unread/', {method: 'GET'});
+  return request('/notification/unread/', { method: 'GET' });
 }
 
 export async function SetNotificationRead(params) {
-  return request(`/notification/read/${params.ID}`, {method: 'PUT'});
+  return request(`/notification/read/${params.ID}`, { method: 'PUT' });
 }
 
 // export async function queryUserList(param) {
@@ -25,14 +28,19 @@ export async function SetNotificationRead(params) {
 // }
 
 export async function bindQywxUserId(param) {
-  return request(`/user/qywx-userId?code=${param.code}&JWT-TOKEN=${param.token}`)
+  return request(`/user/qywx-userId?code=${param.code}&JWT-TOKEN=${param.token}`);
 }
 
 export async function updateUser(param) {
-  return request(`/user/update`,{
-    method:"PUT",
+  return request(`/user/update`, {
+    method: 'PUT',
     body: {
-    ...param,
-  }
-})
+      ...param,
+    },
+  });
+}
+
+//获取部门结构
+export async function queryDepV2(params) {
+  return request(`/api/v2/dep?${stringify(params)}`);
 }

+ 17 - 1
src/services/workHours.js

@@ -79,5 +79,21 @@ export async function queryProjectReport(params) {
 }
 
 export async function queryProjectReportDetail(params) {
-  return request(`/api/v2/workload/project/month/rpt?${stringify(params)}`)
+  return request(`/api/v2/workload/project/month/rpt?${stringify(params)}`);
 }
+
+export async function queryFinanceReport(params) {
+  return request(`/api/v2/workload/finance/rpt?${stringify(params)}`);
+}
+
+export async function queryDepCompare(params) {
+  return request(`/api/v2/workload/dep/compare?${stringify(params)}`);
+}
+
+export async function queryDepCompareUser(params) {
+  return request(`/api/v2/workload/dep/compare/users?${stringify(params)}`);
+}
+
+export async function queryUserProject(params){
+  return request(`/api/v2/workload/dep/compare/users/project?${stringify(params)}`)
+}

+ 10 - 6
src/utils/exportExcl.js

@@ -3,9 +3,8 @@ const Excel = require('exceljs');
 
 import FileSaver from 'file-saver';
 
-export default function exportExcel(luckysheet, value) {
+export function getExcelBolob(luckysheet) {
   // 参数为luckysheet.getluckysheetfile()获取的对象
-  console.log(luckysheet);
   // 1.创建工作簿,可以为工作簿添加属性
   const workbook = new Excel.Workbook();
   // 2.创建表格,第二个参数可以配置创建什么样的工作表
@@ -24,18 +23,23 @@ export default function exportExcel(luckysheet, value) {
     setBorder(borderInfo, worksheet);
     return true;
   });
-
-  // return
-  // 4.写入 buffer
   const buffer = workbook.xlsx.writeBuffer().then(data => {
     // console.log('data', data)
     const blob = new Blob([data], {
       type: 'application/vnd.ms-excel;charset=utf-8',
     });
+    return blob;
+  });
+  return buffer;
+}
+
+export default function exportExcel(luckysheet, value) {
+  // return
+  // 4.写入 buffer
+  getExcelBolob(luckysheet).then(blob => {
     console.log('导出成功!');
     FileSaver.saveAs(blob, `${value}.xlsx`);
   });
-  return buffer;
 }
 
 var setMerge = function(luckyMerge = {}, worksheet) {