xujunjie 3 年之前
父節點
當前提交
defc21ff91
共有 35 個文件被更改,包括 1789 次插入365 次删除
  1. 6 1
      config/router.config.js
  2. 71 48
      src/components/Flow/config-cmd.ts
  3. 21 2
      src/components/Flow/config-toolbar.ts
  4. 29 6
      src/components/Flow/index.tsx
  5. 12 1
      src/components/Flow/node/circle/mapServe.tsx
  6. 4 0
      src/components/Flow/node/fields/index.ts
  7. 3 2
      src/components/Flow/node/fields/input-number.tsx
  8. 39 0
      src/components/Flow/node/fields/radio.tsx
  9. 2 2
      src/components/Flow/node/fields/select.tsx
  10. 39 0
      src/components/Flow/node/fields/upload.tsx
  11. 4 4
      src/components/Flow/node/rect/index.tsx
  12. 123 19
      src/components/Flow/node/rect/mapServe.tsx
  13. 3 3
      src/components/Flow/react-node/CustomRect.js
  14. 18 7
      src/components/Flow/service.ts
  15. 107 0
      src/components/OssUpload/AliyunOssUploader.js
  16. 30 0
      src/models/file.js
  17. 39 0
      src/models/xflow.js
  18. 74 0
      src/pages/PurchaseAdmin/PurchaseList/Detail/CommitAuditModal.js
  19. 26 34
      src/pages/PurchaseAdmin/PurchaseList/Detail/CommitModal.js
  20. 36 11
      src/pages/PurchaseAdmin/PurchaseList/Detail/FlowModal.js
  21. 153 54
      src/pages/PurchaseAdmin/PurchaseList/Detail/Index.js
  22. 2 2
      src/pages/PurchaseAdmin/PurchaseList/Detail/LuckySheet.js
  23. 11 9
      src/pages/PurchaseAdmin/PurchaseList/Detail/RightDrawer.js
  24. 40 0
      src/pages/PurchaseAdmin/PurchaseList/Detail/VersionModal.js
  25. 146 43
      src/pages/PurchaseAdmin/PurchaseList/Detail/models/detail.js
  26. 220 0
      src/pages/PurchaseAdmin/PurchaseList/Flow/Audit.js
  27. 15 0
      src/pages/PurchaseAdmin/PurchaseList/Flow/Audit.less
  28. 40 0
      src/pages/PurchaseAdmin/PurchaseList/Flow/AuditModal.js
  29. 72 0
      src/pages/PurchaseAdmin/PurchaseList/Flow/AuditNodeModal.js
  30. 56 7
      src/pages/PurchaseAdmin/PurchaseList/Flow/Flow.js
  31. 36 109
      src/pages/PurchaseAdmin/PurchaseList/Flow/models/flow.js
  32. 71 0
      src/pages/PurchaseAdmin/PurchaseList/List/NewList.js
  33. 36 0
      src/pages/PurchaseAdmin/PurchaseList/List/models/newList.js
  34. 202 0
      src/services/boom.js
  35. 3 1
      src/utils/request.js

+ 6 - 1
config/router.config.js

@@ -13,7 +13,8 @@ export default [
         routes: [
           {
             path: '/home',
-            component: './PurchaseAdmin/PurchaseList/List/List',
+            component: './PurchaseAdmin/PurchaseList/List/NewList',
+            // component: './PurchaseAdmin/PurchaseList/List/List',
           },
           {
             path: '/home/detail/:excelId/:projectId',
@@ -51,6 +52,10 @@ export default [
             path: '/home/flow',
             component: './PurchaseAdmin/PurchaseList/Flow/Flow',
           },
+          {
+            path: '/home/audit',
+            component: './PurchaseAdmin/PurchaseList/Flow/Audit',
+          },
         ],
       },
       {

+ 71 - 48
src/components/Flow/config-cmd.ts

@@ -6,60 +6,83 @@ import {
   XFlowGraphCommands,
   IApplication,
   IGraphPipelineCommand,
+  MODELS,
+  NsGraph,
 } from '@antv/xflow';
-import { MockApi } from './service';
+// import { MockApi } from './service';
 
-export const useCmdConfig = createCmdConfig(config => {
-  config.setRegisterHookFn(hooks => {
-    const list = [
-      hooks.addNode.registerHook({
-        name: 'set node config',
-        handler: async args => {
-          args.nodeConfig = {
-            ...args.nodeConfig,
-            id: args.nodeConfig.id || `node-${uuidv4()}`,
-          };
-          console.log('add node', args);
-        },
-      }),
-      // hooks.highlightNode.registerHook({
-      //   name: 'highlightNode',
-      //   handler: async args => {
-      //     console.log('highlightNode:', args);
-      //   },
-      // }),
-      hooks.selectNode.registerHook({
-        name: 'selectNode',
-        handler: async args => {
-          console.log('select node:', args);
-        },
-      }),
-    ];
-    const toDispose = new DisposableCollection();
-    toDispose.pushAll(list);
-    return toDispose;
+export const useCmdConfig = props => {
+  const cmdConfig = createCmdConfig(config => {
+    config.setRegisterHookFn(hooks => {
+      const list = [
+        hooks.addNode.registerHook({
+          name: 'set node config',
+          handler: async args => {
+            args.nodeConfig.ports?.items.forEach(item => {
+              item.id = item.id || uuidv4().substring(0, 8);
+            });
+            args.nodeConfig = {
+              ...args.nodeConfig,
+              // ports
+              id: args.nodeConfig.id || `${uuidv4().substring(0, 8)}`,
+            };
+          },
+        }),
+        // hooks.addEdge.registerHook({
+        //   name: 'addEdge',
+        //   handler: async args => {
+        //     args.edgeConfig = {
+        //       ...args.edgeConfig,
+        //       id: `${uuidv4().substring(0, 8)}`,
+        //     };
+        //   },
+        // }),
+        hooks.selectNode.registerHook({
+          name: 'selectNode',
+          handler: async args => {
+            console.log('select node:', args);
+            setTimeout(async () => {
+              let node = await MODELS.SELECTED_NODE.useValue(args.modelService);
+              props.onSelectNode?.(node.getData?.());
+            }, 100);
+          },
+        }),
+        hooks.updateNode.registerHook({
+          name: 'updateNode',
+          handler: async args => {
+            const { nodeConfig } = args;
+            props.onUpdate?.(nodeConfig);
+          },
+        }),
+      ];
+      const toDispose = new DisposableCollection();
+      toDispose.pushAll(list);
+      return toDispose;
+    });
   });
-});
+
+  return cmdConfig();
+};
 
 /** 查询图的节点和边的数据 */
-export const initGraphCmds = (app: IApplication) => {
+export const initGraphCmds = (app: IApplication, graphData: NsGraph.IGraphData) => {
+  if (!app) return;
   app.executeCommandPipeline([
     /** 1. 从服务端获取数据 */
-    {
-      commandId: XFlowGraphCommands.LOAD_DATA.id,
-      getCommandOption: async () => {
-        return {
-          args: {
-            loadDataService: MockApi.loadGraphData,
-          },
-        };
-      },
-    } as IGraphPipelineCommand<NsGraphCmd.GraphLoadData.IArgs>,
+    // {
+    //   commandId: XFlowGraphCommands.LOAD_DATA.id,
+    //   getCommandOption: async () => {
+    //     return {
+    //       args: {
+    //         loadDataService: MockApi.loadGraphData,
+    //       },
+    //     };
+    //   },
+    // } as IGraphPipelineCommand<NsGraphCmd.GraphLoadData.IArgs>,
     /** 3. 画布内容渲染 */
     {
       commandId: XFlowGraphCommands.GRAPH_RENDER.id,
-      getCommandOption: async ctx => {
-        const { graphData } = ctx.getResult();
+      getCommandOption: async () => {
         return {
           args: {
             graphData,
@@ -67,13 +90,13 @@ export const initGraphCmds = (app: IApplication) => {
         };
       },
     } as IGraphPipelineCommand<NsGraphCmd.GraphRender.IArgs>,
-     /** 4. 缩放画布 */
-     {
+    /** 4. 缩放画布 */
+    {
       commandId: XFlowGraphCommands.GRAPH_ZOOM.id,
       getCommandOption: async () => {
         return {
-          args: { factor: 'fit', zoomOptions: { maxScale: 0.9 } },
-        }
+          args: { factor: 'fit', zoomOptions: { maxScale: 1 } },
+        };
       },
     } as IGraphPipelineCommand<NsGraphCmd.GraphZoom.IArgs>,
   ]);

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

@@ -194,8 +194,27 @@ namespace NSToolbarConfig {
           TOOLBAR_ITEMS.SAVE_GRAPH_DATA,
           {
             saveGraphDataService: (meta, graphData) => {
-              console.log(graphData);
-              localStorage.graphData = JSON.stringify(graphData);
+              let data = JSON.parse(JSON.stringify(graphData))
+              data.nodes = data.nodes.map(item => {
+                delete item.incomingEdges;
+                delete item.originData;
+                delete item.outgoingEdges;
+                delete item.ports.groups
+                return item;
+              });
+              // 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 item;
+              });
+              console.log(data);
+              // localStorage.graphData = JSON.stringify(data);
               return null;
             },
           }

+ 29 - 6
src/components/Flow/index.tsx

@@ -24,6 +24,7 @@ import {
   CanvasSnapline,
   /** 通用组件:节点连接桩 */
   CanvasNodePortTooltip,
+  IApplication,
 } from '@antv/xflow';
 import { Graph } from '@antv/x6';
 /** 配置Command*/
@@ -45,24 +46,33 @@ import './index.less';
 
 export interface IProps {
   meta: { flowId: string; type: 'edit' };
+  flowDetail: any;
+  onSelectNode?: Function;
 }
 
 export const Demo: React.FC<IProps> = props => {
-  const { meta } = props;
+  const { meta, flowDetail } = props;
   const isEdit = meta.type == 'edit';
   const toolbarConfig = useToolbarConfig();
   const menuConfig = useMenuConfig();
   const keybindingConfig = useKeybindingConfig();
   const graphRef = useRef<Graph>();
-  const commandConfig = useCmdConfig();
+  const appRef = useRef<IApplication>();
+  const commandConfig: any = useCmdConfig(props);
+
   /**
    * @param app 当前XFlow工作空间
    * @param extensionRegistry 当前XFlow配置项
    */
-
   const onLoad: IAppLoad = async app => {
+    appRef.current = app;
     graphRef.current = await app.getGraphInstance();
-    initGraphCmds(app);
+    renderGraph();
+  };
+
+  const renderGraph = () => {
+    if (flowDetail.nodes.length == 0 || !appRef.current) return;
+    initGraphCmds(appRef.current, flowDetail);
   };
   const getConfig = () => {
     const defaultOption = {
@@ -84,6 +94,13 @@ export const Demo: React.FC<IProps> = props => {
             showNodeSelectionBox: true,
           },
           interacting: false,
+          mousewheel: false,
+          connecting: {
+            highlight: false,
+            allowBlank: false,
+            allowPort: false,
+            dangling: false,
+          },
         };
   };
   useEffect(() => {
@@ -94,6 +111,10 @@ export const Demo: React.FC<IProps> = props => {
     }
   }, [graphRef]);
 
+  useEffect(() => {
+    renderGraph();
+  }, [flowDetail, appRef.current]);
+
   return (
     <XFlow
       className="flow-user-custom-clz"
@@ -103,10 +124,12 @@ export const Demo: React.FC<IProps> = props => {
     >
       <FlowchartNodePanel
         registerNode={{
-          title: '自定义节点',
-          key: '1',
+          title: '节点',
+          key: 'custom',
           nodes: registerNode,
         }}
+        showOfficial={false}
+        defaultActiveKey={['custom']}
         position={{ width: 162, top: 40, bottom: 0, left: isEdit ? 0 : -999 }}
       />
       {isEdit && (

+ 12 - 1
src/components/Flow/node/circle/mapServe.tsx

@@ -1,9 +1,10 @@
 import React, { useState, useEffect } from 'react';
 import { FlowchartFormWrapper } from '@antv/xflow';
-import { Position, Size, ColorPicker, InputNumberFiled } from '../fields';
+import { Position, Size, ColorPicker, InputNumberFiled, InputFiled } from '../fields';
 import { PREFIX } from '../constants';
 
 export interface IConfig {
+  label?: string;
   x?: number;
   y?: number;
   width?: number;
@@ -39,6 +40,16 @@ const Component = (props: any) => {
 
   return (
     <div className={`${PREFIX}-panel-body`}>
+      <div className={`${PREFIX}-panel-group`}>
+        <h5>内容</h5>
+        <InputFiled
+          label="标题"
+          value={nodeConfig.label}
+          onChange={value => {
+            onNodeConfigChange('label', value);
+          }}
+        />
+      </div>
       <div className={`${PREFIX}-panel-group`}>
         <h5>样式</h5>
         <Position

+ 4 - 0
src/components/Flow/node/fields/index.ts

@@ -4,8 +4,10 @@ import InputNumberFiled from './input-number';
 import Size from './size';
 import Position from './position';
 import SelectField from './select';
+import RadioField from './radio';
 import PlcDevice from './plcDevice';
 import ImgSelect from './imgSelect';
+import UploadFiled from './upload';
 
 export {
   InputFiled,
@@ -16,4 +18,6 @@ export {
   SelectField,
   PlcDevice,
   ImgSelect,
+  RadioField,
+  UploadFiled,
 };

+ 3 - 2
src/components/Flow/node/fields/input-number.tsx

@@ -7,13 +7,14 @@ interface IProps {
   value?: number;
   min?: number;
   width?: number;
+  style?: object;
   onChange?: (value: number) => void;
 }
 
 const InputNumberFiled: React.FC<IProps> = (props) => {
-  const { label, value, onChange, min, width } = props;
+  const { label, value, onChange, min, width ,style} = props;
   return (
-    <div className="group">
+    <div className="group" style={style}>
       {label && <label>{label}</label>}
       <InputNumber
         value={value}

+ 39 - 0
src/components/Flow/node/fields/radio.tsx

@@ -0,0 +1,39 @@
+import React from 'react';
+import { Radio } from 'antd';
+import { FormItemHeight } from '../constants';
+
+interface IProps {
+  label?: string;
+  value?: string | number;
+  options?: {
+    label: string | number;
+    value: string | number;
+  }[];
+  width?: number | string;
+  onChange?: (value: string | number) => void;
+}
+
+const SelectField: React.FC<IProps> = props => {
+  const { label = '', value, onChange, options = [], width } = props;
+  return (
+    <div className="group">
+      <label>{label}</label>
+      <Radio.Group
+        value={value}
+        style={{
+          width,
+          height: FormItemHeight,
+        }}
+        onChange={e => {
+          onChange?.(e.target.value);
+        }}
+      >
+        {options.map(item => (
+          <Radio value={item.value}>{item.label}</Radio>
+        ))}
+      </Radio.Group>
+    </div>
+  );
+};
+
+export default SelectField;

+ 2 - 2
src/components/Flow/node/fields/select.tsx

@@ -4,13 +4,13 @@ import { FormItemHeight } from '../constants';
 
 interface IProps {
   label?: string;
-  value?: string;
+  value?: string | number;
   options?: {
     label: string | number;
     value: string | number;
   }[];
   width?: number | string;
-  onChange?: (value: string) => void;
+  onChange?: (value: string | number) => void;
 }
 
 const SelectField: React.FC<IProps> = (props) => {

+ 39 - 0
src/components/Flow/node/fields/upload.tsx

@@ -0,0 +1,39 @@
+import React, { useState, useEffect } from 'react';
+import { connect } from 'dva';
+import AliyunOssUploader from '@/components/OssUpload/AliyunOssUploader';
+
+interface IProps {
+  label?: string;
+  value?: string;
+  OSSData?: any;
+  onChange?: (value: string) => void;
+  beforeUpload?: (file: any) => Boolean;
+  onUploading?: (value: string) => void;
+}
+
+const UploadFiled: React.FC<IProps> = props => {
+  const { label = '标签', value, onChange, onUploading, OSSData, beforeUpload } = props;
+
+  const uploadProps = {
+    OSSData: OSSData,
+    onDone: file => {
+      let url: string = OSSData.host + '/' + file.url;
+      onChange(url);
+    },
+    onUploading: onUploading,
+    beforeUpload: beforeUpload,
+    noStyle: false,
+    showUploadList: false,
+  };
+
+  return (
+    <div className="group">
+      <label>{label}</label>
+      <AliyunOssUploader {...uploadProps} directory={false} label="上传"></AliyunOssUploader>
+    </div>
+  );
+};
+
+export default connect(({ xflow }) => ({
+  OSSData: xflow.OSSData,
+}))(UploadFiled);

+ 4 - 4
src/components/Flow/node/rect/index.tsx

@@ -12,16 +12,16 @@ export default function CustomRect(props) {
   const app = useXFlowApp();
 
   const handleClick = () => {
-    console.log(data);
+    // console.log(data);
     app.executeCommand(XFlowNodeCommands.SELECT_NODE.id, {
       nodeId: data.id,
     });
-    console.log('XFlowNodeCommands.SELECT_NODE.id', data);
-    message.success(`${XFlowNodeCommands.SELECT_NODE.label}: 命令执行成功`);
+    // console.log('XFlowNodeCommands.SELECT_NODE.id', data);
+    // message.success(`${XFlowNodeCommands.SELECT_NODE.label}: 命令执行成功`);
   };
 
   return (
-    <Badge count={data.count}>
+    <Badge count={data.verison?.length}>
       <div
         style={{
           width,

+ 123 - 19
src/components/Flow/node/rect/mapServe.tsx

@@ -1,9 +1,21 @@
 import React, { useState, useEffect } from 'react';
 import { FlowchartFormWrapper } from '@antv/xflow';
-import { Position, Size, ColorPicker, InputNumberFiled } from '../fields';
+import { message } from 'antd';
+import LuckyExcel from 'luckyexcel';
+import {
+  Position,
+  Size,
+  ColorPicker,
+  InputNumberFiled,
+  InputFiled,
+  UploadFiled,
+  RadioField,
+  SelectField,
+} from '../fields';
 import { PREFIX } from '../constants';
 
 export interface IConfig {
+  label?: string;
   x?: number;
   y?: number;
   width?: number;
@@ -13,15 +25,27 @@ export interface IConfig {
   fontFill?: string;
   fill?: string;
   stroke?: string;
+  muti_version?: string | number;
+  is_start_node?: string | number;
+  bom_template?: string;
+  version_name?: string;
+  data?: any;
+  excel_info?: any;
 }
 
+const defaultConfig: IConfig = {
+  muti_version: 1,
+  is_start_node: 0,
+};
+
 const Component = (props: any) => {
   const { config, plugin = {} } = props;
   const { updateNode } = plugin;
   const [nodeConfig, setNodeConfig] = useState<IConfig>({
+    ...defaultConfig,
     ...config,
   });
-  const onNodeConfigChange = (key: string, value: number | string) => {
+  const onNodeConfigChange = (key: string, value: any) => {
     setNodeConfig({
       ...nodeConfig,
       [key]: value,
@@ -31,14 +55,94 @@ const Component = (props: any) => {
     });
   };
 
+  const beforeUpload = (file: any) => {
+    LuckyExcel.transformExcelToLucky(file, (exportJson, luckysheetfile) => {
+      if (exportJson.sheets == null || exportJson.sheets.length == 0) {
+        message.error('读取xlsx文件失败!');
+        return;
+      }
+      console.log(exportJson);
+      const sheet = exportJson.sheets[0];
+      const titleCell = sheet.celldata.filter(item => item.r == 0);
+      let cell = titleCell.map(item => {
+        let value = '';
+        if (item.v?.v) {
+          value = item.v?.v;
+        } else if (item.v.ct?.s && item.v.ct?.s instanceof Array) {
+          value = item.v.ct.s.map(item => item?.v).join?.('');
+        }
+        return { sheet_name: sheet.name, col_idx: item.r, col_axis: item.c, col_value: value };
+      });
+      updateNode({
+        data: [sheet],
+        excel_info: {
+          file_name: file.name,
+          excel_cols: cell,
+        },
+      });
+    });
+  };
+
   useEffect(() => {
     setNodeConfig({
+      ...defaultConfig,
       ...config,
     });
   }, [config]);
 
   return (
     <div className={`${PREFIX}-panel-body`}>
+      <div className={`${PREFIX}-panel-group`}>
+        <h5>内容</h5>
+        <InputFiled
+          label="标题"
+          value={nodeConfig.label}
+          onChange={value => {
+            onNodeConfigChange('label', value);
+          }}
+        />
+      </div>
+      <div className={`${PREFIX}-panel-group`}>
+        <h5>数据</h5>
+        <RadioField
+          label="多个版本"
+          value={nodeConfig.muti_version}
+          onChange={value => {
+            onNodeConfigChange('muti_version', value);
+          }}
+          options={[
+            { label: '是', value: 1 },
+            { label: '否', value: 0 },
+          ]}
+        />
+        <RadioField
+          label="起始起点"
+          value={nodeConfig.is_start_node}
+          onChange={value => {
+            onNodeConfigChange('is_start_node', value);
+          }}
+          options={[
+            { label: '是', value: 1 },
+            { label: '否', value: 0 },
+          ]}
+        />
+        {nodeConfig.is_start_node == 1 && (
+          <>
+            <InputFiled
+              label="版本名称"
+              value={nodeConfig.version_name}
+              onChange={value => {
+                onNodeConfigChange('version_name', value);
+              }}
+            />
+            <UploadFiled
+              label="模板"
+              onChange={url => onNodeConfigChange('bom_template', url)}
+              beforeUpload={beforeUpload}
+            />
+          </>
+        )}
+      </div>
       <div className={`${PREFIX}-panel-group`}>
         <h5>样式</h5>
         <Position
@@ -76,23 +180,23 @@ const Component = (props: any) => {
             onNodeConfigChange('count', value);
           }}
         />
-      </div>
-
-      <div className={`${PREFIX}-node-text-style`}>
-        <InputNumberFiled
-          label="字号"
-          value={nodeConfig.fontSize}
-          width={68}
-          onChange={value => {
-            onNodeConfigChange('fontSize', value);
-          }}
-        />
-        <ColorPicker
-          value={nodeConfig.fontFill}
-          onChange={(value: string) => {
-            onNodeConfigChange('fontFill', value);
-          }}
-        />
+        <div style={{ display: 'flex' }}>
+          <InputNumberFiled
+            label="字号"
+            value={nodeConfig.fontSize}
+            width={68}
+            onChange={value => {
+              onNodeConfigChange('fontSize', value);
+            }}
+            style={{ marginRight: 10 }}
+          />
+          <ColorPicker
+            value={nodeConfig.fontFill}
+            onChange={(value: string) => {
+              onNodeConfigChange('fontFill', value);
+            }}
+          />
+        </div>
       </div>
     </div>
   );

+ 3 - 3
src/components/Flow/react-node/CustomRect.js

@@ -10,12 +10,12 @@ export default function CustomRect(props) {
   const app = useXFlowApp();
 
   const handleClick = () => {
-    console.log(data);
+    // console.log(data);
     app.executeCommand(XFlowNodeCommands.SELECT_NODE.id, {
       nodeId: data.id,
     });
-    console.log('XFlowNodeCommands.SELECT_NODE.id', data);
-    message.success(`${XFlowNodeCommands.SELECT_NODE.label}: 命令执行成功`);
+    // console.log('XFlowNodeCommands.SELECT_NODE.id', data);
+    // message.success(`${XFlowNodeCommands.SELECT_NODE.label}: 命令执行成功`);
   };
 
   return (

+ 18 - 7
src/components/Flow/service.ts

@@ -1,6 +1,7 @@
 import { DND_RENDER_ID, NODE_WIDTH, NODE_HEIGHT } from './constant';
 import { NsGraph } from '@antv/xflow';
 import { NsGraphCmd } from '@antv/xflow';
+import { queryBoomFlowDetail } from '@/services/boom';
 
 /** mock 后端接口调用 */
 export namespace MockApi {
@@ -19,13 +20,23 @@ export namespace MockApi {
   };
   /** 加载图数据的api */
   export const loadGraphData = async (meta: NsGraph.IGraphMeta) => {
-    const graphData = localStorage.graphData
-      ? JSON.parse(localStorage.graphData)
-      : {
-          nodes: [],
-          edges: [],
-        };
-    return graphData;
+    const { data } = await queryBoomFlowDetail({ id: meta.flowId });
+
+    
+    // graphData.nodes = graphData.nodes.map(item => {
+    //   item.ports.groups = groups;
+    //   return item;
+    // });
+    // // graphData.edges = []
+    // graphData.edges = graphData.edges.map(item => {
+    //   item.attrs = attrs;
+    //   return item;
+    // });
+
+    return {
+      // nodes,
+      // edges,
+    };
   };
   /** 保存图数据的api */
   export const saveGraphData: NsGraphCmd.SaveGraphData.IArgs['saveGraphDataService'] = async (

+ 107 - 0
src/components/OssUpload/AliyunOssUploader.js

@@ -0,0 +1,107 @@
+import React from 'react';
+import { Upload, message, Button } from 'antd';
+import LuckyExcel from 'luckyexcel';
+
+class AliyunOSSUpload extends React.Component {
+  state = {
+    OSSData: {},
+  };
+
+  componentDidMount() {
+    this.init();
+  }
+
+  init = () => {
+    try {
+      const { OSSData } = this.props;
+
+      this.setState({
+        OSSData,
+      });
+    } catch (error) {
+      message.error(error);
+    }
+  };
+
+  onChange = ({ file, fileList }) => {
+    const { onChange, onDone, onUploading } = this.props;
+    console.log('Aliyun OSS:', file, fileList);
+    if (onChange) {
+      onChange([...fileList]);
+    }
+
+    if (onDone) {
+      if (file.status === 'done') onDone(file);
+    }
+    if (onUploading && file.status === 'uploading') {
+      onUploading({ file, fileList });
+    }
+    this.setState({ fileList: [...fileList] });
+  };
+
+  onRemove = file => {
+    const { value, onChange } = this.props;
+
+    const files = value.filter(v => v.url !== file.url);
+
+    if (onChange) {
+      onChange(files);
+    }
+  };
+
+  transformFile = file => {
+    const { OSSData } = this.state;
+    file.url = OSSData.dir + '/' + file.name;
+    return file;
+  };
+
+  getExtraData = file => {
+    const { OSSData } = this.state;
+
+    return {
+      key: file.url,
+      OSSAccessKeyId: OSSData.accessid,
+      policy: OSSData.policy,
+      Signature: OSSData.signature,
+    };
+  };
+
+  beforeUpload = async file => {
+    const { OSSData } = this.state;
+    const expire = OSSData.expire * 1000;
+    console.log(file);
+
+    if (expire < Date.now()) {
+      await this.init();
+    }
+    if (this.props.beforeUpload) {
+      return this.props.beforeUpload(file);
+    }
+
+    return true;
+  };
+
+  render() {
+    const { value, directory, label, noStyle, showUploadList, accept } = this.props;
+    const props = {
+      name: 'file',
+      fileList: this.state.fileList,
+      action: this.state.OSSData.host,
+      onChange: this.onChange,
+      onRemove: this.onRemove,
+      transformFile: this.transformFile,
+      data: this.getExtraData,
+      beforeUpload: this.beforeUpload,
+      accept: accept,
+      showUploadList: showUploadList !== false,
+      headers: { 'Access-Control-Allow-Origin': '*' },
+    };
+    return (
+      <Upload {...props} directory={directory}>
+        {noStyle ? label : <Button type="primary">{label}</Button>}
+      </Upload>
+    );
+  }
+}
+
+export default AliyunOSSUpload;

+ 30 - 0
src/models/file.js

@@ -0,0 +1,30 @@
+import { queryOSSData } from '@/services/boom';
+import { message } from 'antd';
+
+export default {
+  namespace: 'file',
+  state: {
+    OSSData: {},
+  },
+
+  effects: {
+    *queryOSSData({}, { call, put }) {
+      const response = yield call(queryOSSData);
+      if (response) {
+        yield put({
+          type: 'save',
+          payload: { OSSData: response.data },
+        });
+      }
+    },
+  },
+
+  reducers: {
+    save(state, action) {
+      return {
+        ...state,
+        ...action.payload,
+      };
+    },
+  },
+};

+ 39 - 0
src/models/xflow.js

@@ -0,0 +1,39 @@
+import { queryOSSData,queryBoomFlowDetail } from '@/services/boom';
+import { message } from 'antd';
+
+export default {
+  namespace: 'xflow',
+  state: {
+    OSSData: {},
+    flowDetail: { nodes: [], edges: [] },
+  },
+
+  effects: {
+    *queryOSSData({}, { call, put }) {
+      const response = yield call(queryOSSData);
+      if (response) {
+        yield put({
+          type: 'save',
+          payload: { OSSData: response.data },
+        });
+      }
+    },
+    *queryBoomFlowDetail({ payload }, { call, put }) {
+      const data = yield call(queryBoomFlowDetail, payload);
+      console.log(data);
+      yield put({
+        type: 'save',
+        payload: { flowDetail: data },
+      });
+    },
+  },
+
+  reducers: {
+    save(state, action) {
+      return {
+        ...state,
+        ...action.payload,
+      };
+    },
+  },
+};

+ 74 - 0
src/pages/PurchaseAdmin/PurchaseList/Detail/CommitAuditModal.js

@@ -0,0 +1,74 @@
+import React, { useEffect, useState, useRef, useMemo } from 'react';
+import { Form } from '@ant-design/compatible';
+import '@ant-design/compatible/assets/index.css';
+import { Modal, Input, Select } from 'antd';
+import { connect } from 'dva';
+
+const { TextArea } = Input;
+
+// 提交
+function CommitModal(props) {
+  const { visible, onClose, onOk, form, loading, version, flowDetail } = props;
+
+  const handleOk = () => {
+    form.validateFields((err, fieldsValue) => {
+      if (err) return;
+      onOk(fieldsValue);
+    });
+  };
+  const currentNodeId = useMemo(() => {
+    let Id = version.template_node_id
+    return flowDetail.nodes.find?.(item => item.Id == Id)
+  },[flowDetail,version])
+
+  /**
+   *
+   * @param {*} currentId 当前节点
+   * @param {*} type 下一个节点的类型  custom-circle: 审批节点   custom-rect: 业务节点
+   * @returns
+   */
+  const getNextNodes = (currentId, type) => {
+    const { edges, nodes } = flowDetail;
+    if (!currentId) return;
+    let targetIds = edges
+      .filter(edge => edge.source.cell == currentId)
+      .map(item => item.target.cell);
+    let auditNodes = nodes.filter(node => node.name == type && targetIds.indexOf(node.id) != -1);
+    return auditNodes || [];
+  };
+
+  return (
+    <Modal
+      confirmLoading={loading}
+      destroyOnClose
+      title="提交流转目标"
+      visible={visible}
+      onCancel={onClose}
+      onOk={handleOk}
+    >
+      <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label="审批节点">
+        {form.getFieldDecorator('version_name')(
+          <Select style={{ width: '100%' }}>
+            {/* <Option>节点1</Option> */}
+            {/* {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('description')(
+          <Select style={{ width: '100%' }}>
+            {/* <Option>节点1</Option> */}
+            {/* {getNextNodes(auditId,"custom-rect").map(item => <Option key={item.Id}>{item.label}</Option>)} */}
+          </Select>
+        )}
+      </Form.Item>
+      <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label="备注信息">
+        {form.getFieldDecorator('description')(<Input.TextArea />)}
+      </Form.Item>
+    </Modal>
+  );
+}
+
+export default connect(({ xflow }) => ({ flowDetail: xflow.flowDetail }))(
+  Form.create()(CommitModal)
+);

+ 26 - 34
src/pages/PurchaseAdmin/PurchaseList/Detail/CommitModal.js

@@ -3,43 +3,35 @@ import { Form } from '@ant-design/compatible';
 import '@ant-design/compatible/assets/index.css';
 import { Modal, Input, Select } from 'antd';
 
-const { TextArea } = Input;
-
 // 提交
 function CommitModal(props) {
-	const { visible, onClose, onOk, form, loading } = props;
+  const { visible, onClose, onOk, form, version, loading } = props;
 
-	const handleOk = () => {
-		form.validateFields((err, fieldsValue) => {
-			if (err) return;
-			onOk(fieldsValue);
-		});
-	};
+  const handleOk = () => {
+    form.validateFields((err, fieldsValue) => {
+      if (err) return;
+      fieldsValue.new_version = version.version_id;
+      onOk(fieldsValue);
+    });
+  };
 
-	return (
-		<Modal
-			confirmLoading={loading}
-			destroyOnClose
-			title="提交流转目标"
-			visible={visible}
-			onCancel={onClose}
-			onOk={handleOk}
-		>
-			<Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="审批节点">
-				{form.getFieldDecorator('version_name')(<Select>
-					<Option>节点1</Option>
-				</Select>)}
-			</Form.Item>
-			<Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="业务节点">
-				{form.getFieldDecorator('description')(<Select>
-					<Option>节点1</Option>
-				</Select>)}
-			</Form.Item>
-			<Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="备注信息">
-				{form.getFieldDecorator('description')(<Input.TextArea />)}
-			</Form.Item>
-		</Modal>
-	);
+  return (
+    <Modal
+      confirmLoading={loading}
+      destroyOnClose
+      title="提交"
+      visible={visible}
+      onCancel={onClose}
+      onOk={handleOk}
+    >
+      <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="标题">
+        {form.getFieldDecorator('version_name')(<Input />)}
+      </Form.Item>
+      <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="详情">
+        {form.getFieldDecorator('description')(<Input.TextArea />)}
+      </Form.Item>
+    </Modal>
+  );
 }
 
-export default Form.create()(CommitModal)
+export default Form.create()(CommitModal);

+ 36 - 11
src/pages/PurchaseAdmin/PurchaseList/Detail/FlowModal.js

@@ -1,17 +1,17 @@
-import React, { useEffect, useState, useRef } from 'react';
-import { Modal, Input, Select } from 'antd';
-import Flow from "@/components/Flow/index"
+import React, { useEffect, useState, useRef, useMemo } from 'react';
+import { Modal, Input, Select, List } 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';
 
 const { TextArea } = Input;
 const localData = JSON.parse(localStorage.ggDetaiData || '{}');
 
 // 提交
 function FlowModal(props) {
-  const { visible, onClose, onOk, form, loading } = props;
-
+  const { visible, onClose, onChangeVersion, form, loading, flowDetail, versionList } = props;
+  const [data, setData] = useState([]);
   const handleOk = () => {
     form.validateFields((err, fieldsValue) => {
       if (err) return;
@@ -19,8 +19,19 @@ function FlowModal(props) {
     });
   };
 
+  const graphData = useMemo(() => {
+    let nodes = flowDetail.nodes.map(item => ({
+      ...item,
+      verison: versionList.filter(version => version.template_node_id == item.Id) || [],
+    }));
+    return {
+      nodes,
+      edges: flowDetail.edges,
+    };
+  }, [flowDetail, versionList]);
+
   const handleSelectNode = node => {
-    console.log(node);
+    setData(node.version);
   };
 
   return (
@@ -30,13 +41,27 @@ function FlowModal(props) {
       title="流程图"
       visible={visible}
       onCancel={onClose}
-      onOk={handleOk}
+      footer={false}
       width="80%"
     >
-      <Flow meta={{ type: 'view' }} />
+      <Flow meta={{ type: 'view' }} flowDetail={flowDetail} onSelectNode={handleSelectNode} />
+      <List
+        size="small"
+        header={<div>版本列表</div>}
+        bordered
+        dataSource={data}
+        style={{ marginTop: 20 }}
+        renderItem={item => (
+          <List.Item actions={[<a onClick={() => onChangeVersion(item)}>切换</a>]}>
+            {item.version_name}
+          </List.Item>
+        )}
+      />
     </Modal>
   );
 }
 
-
-export default FlowModal;
+export default connect(({ xflow, detail }) => ({
+  flowDetail: xflow.flowDetail,
+  versionList: detail.versionList,
+}))(FlowModal);

+ 153 - 54
src/pages/PurchaseAdmin/PurchaseList/Detail/Index.js

@@ -15,6 +15,8 @@ import FlowModal from './FlowModal';
 import HistoryModal from './HistoryModal';
 import TimeNode from './TimeNode';
 import FilesModal from './FilesModal';
+import VersionModal from './VersionModal';
+import CommitAuditModal from './CommitAuditModal';
 import { GetTokenFromUrl, getToken } from '@/utils/utils';
 
 const LocalData = localStorage.luckysheet;
@@ -32,6 +34,8 @@ function Detail(props) {
     currentUser,
     fileList,
     roleList,
+    template,
+    versionList,
     match: { params },
   } = props;
   // const audit_status = 0
@@ -43,10 +47,13 @@ function Detail(props) {
   const [commitVisible, setCommitVisible] = useState(false);
   const [auditVisible, setAuditVisible] = useState(false);
   const [flowVisible, setFlowVisible] = useState(false);
+  const [versionVisible, setVersionVisible] = useState(false);
+  const [commitAuditVisible, setCommitAuditVisible] = useState(false);
   const [sheet, setSheet] = useState({});
   const [compareList, setCompareList] = useState([]);
   const [edit, setEdit] = useState(false);
   const [isMerge, setIsMerge] = useState(false);
+  const [version, setVersion] = useState({});
   const [updateCount, setUpdateCount] = useState({
     diff: 0,
     add: 0,
@@ -129,7 +136,7 @@ function Detail(props) {
 
   const renderSheetDom = (item, index) => {
     return (
-      <div key={item.id || 'temp'} className={styles.sheetItem}>
+      <div key={item?.id || 'temp'} className={styles.sheetItem}>
         <h3>{item?.name}</h3>
         <LuckySheet
           className={styles.sheet}
@@ -171,46 +178,75 @@ function Detail(props) {
     }
   };
 
-  const onCommit = values => {
-    var flowNode = flow.currentNode;
-
-    // 获取最新sheet页信息
+  const onCommit = (values, new_version) => {
+    let currentData = sheetRef.current.getSheetJson().data;
+    let params = {
+      ...values,
+      // id: version.id,
+      project_id: version.project_id,
+      name: version.name,
+      guid: version.guid,
+      audit_status: version.audit_status,
+      template_id: version.template_id,
+      template_node_id: version.template_node_id,
+      flow_id: version.flow_id,
+      node_id: version.node_id,
+      new_version: String(new_version),
+      data: JSON.stringify(currentData),
+    };
     dispatch({
-      type: 'detail/querySheet',
-      payload: {
-        project_id: projectId,
-        flow_id: flowNode.flow_id,
-        node_id: flowNode.id,
-      },
+      type: 'detail/commitSheet',
+      payload: params,
       callback: sheets => {
-        let currentData = sheetRef.current.getSheetJson().data;
-        currentData.forEach((item, index) => {
-          // 使用最新的sheetId  防止多次创建sheet
-          item.id = sheets.data[index]?.id;
-          console.log(item.data.celldata);
-        });
-        let newSheet = {
-          ...values,
-          data: JSON.stringify(currentData),
-          id: excelId,
-          flow_id: flowNode.flow_id,
-          node_id: flowNode.id,
-          project_id: projectId,
-        };
-        dispatch({
-          type: 'detail/commitSheet',
-          payload: newSheet,
-          callback: sheets => {
-            onCompare(false);
-            setCommitVisible(false);
-            setSheet(sheets);
-
-            localStorage.luckysheet = JSON.stringify(sheets);
-          },
-        });
+        onCompare(false);
+        setCommitVisible(false);
+        setVersionVisible(false);
+        // setSheet(sheets);
+
+        // localStorage.luckysheet = JSON.stringify(sheets);
       },
     });
   };
+  // const onCommit = values => {
+  //   var flowNode = flow.currentNode;
+
+  //   // 获取最新sheet页信息
+  //   dispatch({
+  //     type: 'detail/querySheet',
+  //     payload: {
+  //       project_id: projectId,
+  //       flow_id: flowNode.flow_id,
+  //       node_id: flowNode.id,
+  //     },
+  //     callback: sheets => {
+  //       let currentData = sheetRef.current.getSheetJson().data;
+  //       currentData.forEach((item, index) => {
+  //         // 使用最新的sheetId  防止多次创建sheet
+  //         item.id = sheets.data[index]?.id;
+  //         console.log(item.data.celldata);
+  //       });
+  //       let newSheet = {
+  //         ...values,
+  //         data: JSON.stringify(currentData),
+  //         id: excelId,
+  //         flow_id: flowNode.flow_id,
+  //         node_id: flowNode.id,
+  //         project_id: projectId,
+  //       };
+  //       dispatch({
+  //         type: 'detail/commitSheet',
+  //         payload: newSheet,
+  //         callback: sheets => {
+  //           onCompare(false);
+  //           setCommitVisible(false);
+  //           setSheet(sheets);
+
+  //           localStorage.luckysheet = JSON.stringify(sheets);
+  //         },
+  //       });
+  //     },
+  //   });
+  // };
 
   const onAudit = ({ audit_comment }) => {
     var flowNode = flow.currentNode;
@@ -309,8 +345,6 @@ function Detail(props) {
   const handleClickCommit = async () => {
     const { list } = await queryHistory();
     let lastCommit = list[0];
-    // setCommitVisible(true);
-    // return;
     // 判断当前版本是否为最新版本
     if (lastCommit && sheet.guid != lastCommit.guid) {
       Modal.confirm({
@@ -376,7 +410,7 @@ function Detail(props) {
     return false;
   };
 
-  function handleMenuClick(e) {
+  const handleMenuClick = e => {
     console.log('click', e);
     switch (e.key) {
       case 'back':
@@ -412,10 +446,15 @@ function Detail(props) {
       case 'edit':
         // 编辑
         handleEdit(true);
+      case 'merge':
+        // 同步版本
+
         break;
       case 'commit':
         // 提交
-        handleClickCommit();
+        // handleClickCommit();
+        setCommitVisible(true);
+        setCommentVisible(false);
         break;
       case 'approval':
         // 申请审批
@@ -427,7 +466,7 @@ function Detail(props) {
         queryFiles();
         break;
     }
-  }
+  };
 
   const renderBtns = () => {
     if (edit) {
@@ -467,14 +506,15 @@ function Detail(props) {
       <Menu.Item key="attachment">附件</Menu.Item>,
     ];
     if (audit_status != 1) {
-      menuList.push(<Menu.Item key="version">版本</Menu.Item>);
-      menuList.push(<Menu.Item key="template">模板</Menu.Item>);
+      menuList.push(<Menu.Item key="version">历史提交</Menu.Item>);
+      // menuList.push(<Menu.Item key="template">模板</Menu.Item>);
     }
     if (isAuditor) {
       menuList.push(<Menu.Item key="auditSuccess">审批通过</Menu.Item>);
       menuList.push(<Menu.Item key="auditFailed">审批拒绝</Menu.Item>);
     } else if (canEdit()) {
       menuList.push(<Menu.Item key="edit">编辑</Menu.Item>);
+      menuList.push(<Menu.Item key="merge">合并</Menu.Item>);
       menuList.push(<Menu.Item key="commit">提交</Menu.Item>);
       if (history.list.length > 0) {
         menuList.push(<Menu.Item key="approval">申请审批</Menu.Item>);
@@ -638,7 +678,42 @@ function Detail(props) {
     window.location.href = `${record.url}`;
   };
 
+  const onChangeVersion = version => {
+    // TODO 查询version对应内容,渲染到界面上
+  };
+
+  const changeVersion = id => {
+    let version = versionList.find(item => item.id == id);
+    setVersion(version);
+    sheetRef.current.renderSheet([]);
+    dispatch({
+      type: 'detail/queryRecord',
+      payload: {
+        project_id: projectId,
+        template_id: version.template_id,
+        template_node_id: version.template_node_id,
+        version_id: version.id,
+      },
+      callback: sheets => {
+        sheetRef.current.renderSheet(sheets.data);
+      },
+    });
+  };
+
   useEffect(() => {
+    dispatch({
+      type: 'detail/queryProjectRecord',
+      payload: {
+        project_id: projectId,
+      },
+    });
+    dispatch({
+      type: 'xflow/queryBoomFlowDetail',
+      payload: {
+        id: 1,
+      },
+    });
+
     // 查询节点
     // dispatch({
     //   type: 'detail/queryFlowInfo',
@@ -704,30 +779,37 @@ function Detail(props) {
         查看流程
       </Button>
       <div className={styles.top}>
-        <div>当前节点/当前状态</div>
+        <div>
+          当前节点: {version.template_node_id}/当前状态:{version.audit_status}
+        </div>
         <div className={styles.btns}>
           {renderBtns()}
-          <Button type="primary" onClick={() => handleClickCommit()}>
+          <Button type="primary" onClick={() => setCommitAuditVisible(true)}>
             提交流转
           </Button>
-          <Button type="primary">创建版本</Button>
-          <Select style={{ width: 140 }} defaultValue={1}>
-            <Option value={1}>版本1</Option>
-            <Option value={2}>版本2</Option>
-            <Option value={3}>版本3</Option>
+          <Button type="primary" onClick={() => setVersionVisible(true)}>
+            创建版本
+          </Button>
+          <Select style={{ width: 140 }} value={version.id} onChange={changeVersion}>
+            {versionList.map(item => (
+              <Option value={item.id} key={item.id}>
+                {item.version_name}
+              </Option>
+            ))}
           </Select>
         </div>
-
         {/* <TimeNode flow={flow}></TimeNode>
-        <div style={{ marginTop: 20 }}>{renderAlert()}</div>
+       
         <div className={styles.btns}>{renderBtns()}</div>
+         */}
         <input
           type="file"
           ref={fileRef}
           style={{ display: 'none' }}
           onChange={e => exportExcl(e.target.files)}
-        /> */}
+        />
       </div>
+      <div style={{ marginTop: 20 }}>{renderAlert()}</div>
       {/* 判断是否为比对模式 */}
       {compareList.length == 2 ? (
         <>
@@ -770,13 +852,15 @@ function Detail(props) {
       <CommitModal
         loading={getLoading()}
         visible={commitVisible}
+        version={version}
         onClose={() => setCommitVisible(false)}
-        onOk={onCommit}
+        onOk={values => onCommit(values, version.id)}
       />
       <FlowModal
         visible={flowVisible}
         onClose={() => setFlowVisible(false)}
         // onOk={onCommit}
+        onChangeVersion={onChangeVersion}
       />
       <AuditModal
         loading={getLoading()}
@@ -793,6 +877,19 @@ function Detail(props) {
         downloadFile={downloadFile}
         data={fileList}
       />
+      <VersionModal
+        loading={getLoading()}
+        visible={versionVisible}
+        onClose={() => setVersionVisible(false)}
+        onOk={values => onCommit(values, 0)}
+      />
+      <CommitAuditModal
+        loading={getLoading()}
+        visible={commitAuditVisible}
+        version={version}
+        onClose={() => setCommitAuditVisible(false)}
+        onOk={() => {}}
+      />
     </Spin>
   );
 }
@@ -804,5 +901,7 @@ export default connect(({ detail, user, loading }) => ({
   comment: detail.comment,
   currentUser: user.currentUser,
   roleList: detail.roleList,
+  version: detail.version,
+  versionList: detail.versionList,
   loading,
 }))(Detail);

+ 2 - 2
src/pages/PurchaseAdmin/PurchaseList/Detail/LuckySheet.js

@@ -180,7 +180,7 @@ class LuckySheet extends React.Component {
         celldata1.forEach(item => {
           // 不判断空字符串
           if (this.isEmpty(item)) return;
-          var d2Item = celldata2.find(item2 => item2.v.cid == item.v.cid);
+          var d2Item = celldata2.find(item2 => item2.c == item.c && item2.r == item.r);
           if (d2Item && !this.isEmpty(d2Item)) {
             // v.ct.s相同,不做处理
             if (item.v.ct?.s && JSON.stringify(item.v.ct?.s) == JSON.stringify(d2Item.v.ct?.s))
@@ -229,7 +229,7 @@ class LuckySheet extends React.Component {
       let celldata1 = sheet.celldata;
       let celldata2 = mergeData[index].celldata;
       celldata2.forEach(item => {
-        var d2Item = celldata1.find(item2 => item2.v.cid == item.v.cid);
+        var d2Item = celldata1.find(item2 => item2.r == item.r && item2.c == item2.c);
         if (!d2Item) {
           delete item.v.v.bg;
           // 将新增项添加至当前文档

+ 11 - 9
src/pages/PurchaseAdmin/PurchaseList/Detail/RightDrawer.js

@@ -21,7 +21,7 @@ function RightDrawer(props) {
     addComment(value, callback);
   };
   return (
-    <Drawer title="评论列表" mask={false} placement="right" onClose={onClose} visible={visible}>
+    <Drawer width={600} title="评论列表" mask={false} placement="right" onClose={onClose} visible={visible}>
       <BomContetn />
       <CommentContent
         title="BOM沟通记录"
@@ -29,19 +29,21 @@ function RightDrawer(props) {
         onSubmit={handleSubmitBom}
         loading={loading}
       />
-      <CommentContent
-        title="单元格沟通记录"
-        list={list}
-        onSubmit={handleSubmitCell}
-        loading={loading}
-      />
+      {list.length > 0 && (
+        <CommentContent
+          title="单元格沟通记录"
+          list={list}
+          onSubmit={handleSubmitCell}
+          loading={loading}
+        />
+      )}
     </Drawer>
   );
 }
 function BomContetn(props) {
   return (
     <Card title="BOM属性" type="inner">
-      <Descriptions>
+      <Descriptions column={2}>
         <Descriptions.Item label="版本名称">XXXXX</Descriptions.Item>
         <Descriptions.Item label="创建人">XXXXX</Descriptions.Item>
         <Descriptions.Item label="创建时间">XXXXX</Descriptions.Item>
@@ -56,7 +58,7 @@ function CommentContent(props) {
   const [value, setValue] = useState('');
 
   return (
-    <Card title={title} type="inner">
+    <Card title={title} type="inner" style={{marginTop: 20}}>
       <List
         className="comment-list"
         itemLayout="horizontal"

+ 40 - 0
src/pages/PurchaseAdmin/PurchaseList/Detail/VersionModal.js

@@ -0,0 +1,40 @@
+import React, { useEffect } from 'react';
+import { Modal, Input, Form } from 'antd';
+
+// 新建流程
+function VersionModal(props) {
+  const { visible, onClose, onOk, userList = [], data = {}, loading } = props;
+  const [form] = Form.useForm();
+  const formLayout = { labelCol: { span: 4 }, wrapperCol: { span: 14 } };
+
+  const handleOk = async () => {
+    let fieldsValue = await form.validateFields();
+    fieldsValue.new_version = 0;
+    onOk(fieldsValue);
+  };
+
+  useEffect(() => {
+    if (visible) form.resetFields();
+  }, [visible]);
+  return (
+    <Modal
+      confirmLoading={loading}
+      destroyOnClose
+      title="新建流程"
+      visible={visible}
+      onCancel={onClose}
+      onOk={handleOk}
+    >
+      <Form {...formLayout} form={form}>
+        <Form.Item label="名称" name="version_name">
+          <Input />
+        </Form.Item>
+        <Form.Item label="详情" name="description">
+          <Input.TextArea />
+        </Form.Item>
+      </Form>
+    </Modal>
+  );
+}
+
+export default VersionModal;

+ 146 - 43
src/pages/PurchaseAdmin/PurchaseList/Detail/models/detail.js

@@ -1,6 +1,6 @@
 import {
   queryFlowInfo,
-  commitSheet,
+  // commitSheet,
   querySheet,
   queryHistory,
   queryHistoryDetail,
@@ -12,11 +12,16 @@ import {
   submitAudit,
   approve,
   queryFiles,
-  deleteFiles
+  deleteFiles,
 } from '@/services/PurchaseList';
 import {
-  queryRole
-} from '@/services/SysAdmin';
+  queryVersionsList,
+  queryProjectRecord,
+  queryRecord,
+  commitSheet,
+  queryDetail,
+} from '@/services/boom';
+import { queryRole } from '@/services/SysAdmin';
 import { setCurrentUser } from '@/utils/authority';
 import { queryProjectMenu } from '@/services/SysAdmin';
 import { storeToken } from '@/utils/utils';
@@ -26,6 +31,10 @@ import { message } from 'antd';
 export default {
   namespace: 'detail',
   state: {
+    template: {},
+    version: {},
+    versionList: [],
+
     flow: {
       active: 0,
       currentNode: {},
@@ -51,6 +60,101 @@ export default {
   },
 
   effects: {
+    *queryProjectRecord({ payload, callback }, { call, put }) {
+      const response = yield call(queryProjectRecord, payload);
+      if (response) {
+        console.log(response);
+        const flow = response.data.list[0];
+        if (flow) {
+          let { template_id, template_node_id } = flow;
+          yield put({
+            type: 'save',
+            payload: { template: flow },
+          });
+          yield put({
+            type: 'queryVersionsList',
+            payload: {
+              ...payload,
+              template_id,
+              template_node_id,
+            },
+            callback,
+          });
+        }
+        // yield put({
+        //   type: 'save',
+        //   payload: { flow: flow },
+        // });
+      }
+    },
+    *queryVersionsList({ payload, callback }, { call, put }) {
+      const response = yield call(queryVersionsList, payload);
+      if (response) {
+        let version = response.data[0];
+        if (version) {
+          yield put({
+            type: 'save',
+            payload: {
+              version,
+              versionList: response.data,
+            },
+          });
+        }
+      }
+    },
+    // 查询子版本详情
+    *queryRecord({ payload, callback }, { call, put }) {
+      const response = yield call(queryRecord, payload);
+      if (response && response.data) {
+        let sheet = response.data;
+        sheet.data = JSON.parse(sheet.data || '[]');
+        sheet.data.forEach(item => {
+          item.config = JSON.parse(item.config || '{}');
+          item.celldata = JSON.parse(item.cell_data || '[]');
+          delete item.cell_data;
+        });
+        callback && callback(sheet);
+      } else {
+        yield put({
+          type: 'queryDetail',
+          payload: {
+            excel_id: payload.version_id,
+          },
+          callback,
+        });
+      }
+    },
+    *commitSheet({ payload, callback }, { call, put }) {
+      let response = yield call(commitSheet, payload);
+      if (response) {
+        callback && callback();
+        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,
+        });
+      }
+    },
+    *queryDetail({ payload, callback }, { call, put }) {
+      const response = yield call(queryDetail, payload);
+      if (response) {
+        let sheet = response.data;
+        sheet.data = JSON.parse(sheet.data || '[]');
+        sheet.data.forEach(item => {
+          item.config = JSON.parse(item.config || '{}');
+          item.celldata = JSON.parse(item.cell_data || '[]');
+          delete item.cell_data;
+        });
+        callback?.(sheet);
+      }
+    },
+
+    // =================================================================================================================================
     *queryFlowInfo({ payload, callback }, { call, put }) {
       const response = yield call(queryFlowInfo, payload);
       if (response) {
@@ -100,17 +204,17 @@ export default {
         });
       }
     },
-    *queryHistory({ payload, callback }, { call, put }) {
-      payload.pageSize = '9999'
-      const response = yield call(queryHistory, payload);
-      if (response) {
-        yield put({
-          type: 'save',
-          payload: { history: response.data },
-        });
-        callback(response.data);
-      }
-    },
+    // *queryHistory({ payload, callback }, { call, put }) {
+    //   payload.pageSize = '9999';
+    //   const response = yield call(queryHistory, payload);
+    //   if (response) {
+    //     yield put({
+    //       type: 'save',
+    //       payload: { history: response.data },
+    //     });
+    //     callback(response.data);
+    //   }
+    // },
     *submitAudit({ payload, callback }, { call, put }) {
       const response = yield call(submitAudit, payload);
       if (response) {
@@ -206,33 +310,33 @@ export default {
         });
       }
     },
-    *commitSheet({ payload, callback }, { call, put }) {
-      const response = yield call(commitSheet, payload);
-      if (response) {
-        const res = yield call(queryHistory, {
-          excel_id: payload.id,
-          project_id: payload.project_id,
-        });
-        yield put({
-          type: 'save',
-          payload: { history: res.data },
-        });
-        const lastCommit = res.data.list[0];
-        message.success('提交成功');
-        yield put({
-          type: 'queryHistoryDetail',
-          payload: {
-            excel_id: lastCommit.excel_id,
-            history_id: lastCommit.id,
-          },
-          callback: sheets => {
-            lastCommit.data = sheets;
-            lastCommit.name = lastCommit.version_name;
-            callback(lastCommit);
-          },
-        });
-      }
-    },
+    // *commitSheet({ payload, callback }, { call, put }) {
+    //   const response = yield call(commitSheet, payload);
+    //   if (response) {
+    //     const res = yield call(queryHistory, {
+    //       excel_id: payload.id,
+    //       project_id: payload.project_id,
+    //     });
+    //     yield put({
+    //       type: 'save',
+    //       payload: { history: res.data },
+    //     });
+    //     const lastCommit = res.data.list[0];
+    //     message.success('提交成功');
+    //     yield put({
+    //       type: 'queryHistoryDetail',
+    //       payload: {
+    //         excel_id: lastCommit.excel_id,
+    //         history_id: lastCommit.id,
+    //       },
+    //       callback: sheets => {
+    //         lastCommit.data = sheets;
+    //         lastCommit.name = lastCommit.version_name;
+    //         callback(lastCommit);
+    //       },
+    //     });
+    //   }
+    // },
     *querySheet({ payload, callback }, { call, put }) {
       const response = yield call(querySheet, payload);
       if (response) {
@@ -282,7 +386,6 @@ export default {
     },
   },
 
-
   reducers: {
     save(state, action) {
       return {

+ 220 - 0
src/pages/PurchaseAdmin/PurchaseList/Flow/Audit.js

@@ -0,0 +1,220 @@
+import React, { useState, useEffect } from 'react';
+import { Form, Select, Button, Table, Input, Checkbox, Divider } from 'antd';
+import { connect } from 'dva';
+import AuditNodeModal from './AuditNodeModal';
+import AuditModal from './AuditModal';
+import styles from './Audit.less';
+
+const { Option } = Select;
+
+function Audit(props) {
+  const { userList, list = [], dispatch } = props;
+  const [form] = Form.useForm();
+  const [visible, setVisible] = useState({
+    audit: false,
+    auditNode: false,
+  });
+  const [current, setCurrent] = useState({});
+  const [currentNode, setCurrentNode] = useState({});
+  const columns = [
+    {
+      title: '节点名',
+      dataIndex: 'node',
+    },
+    {
+      title: '审批级别',
+      dataIndex: 'seq',
+    },
+    {
+      title: '审批人',
+      dataIndex: ['AuditorUser', 'CName'],
+    },
+    {
+      title: '审批关系',
+      dataIndex: 'seq_relate',
+      render: relate => {
+        switch (relate) {
+          case 0:
+            return '无';
+          case 1:
+            return '或';
+          case 2:
+            return '并';
+        }
+      },
+    },
+    {
+      title: '操作',
+      render: (item, index) => (
+        <>
+          <a
+            onClick={() => {
+              setCurrentNode(item);
+              changeVisible('auditNode', true);
+            }}
+          >
+            编辑
+          </a>
+          <Divider type="vertical" />
+          <a
+            onClick={() => {
+              handleDelete(index);
+            }}
+          >
+            删除
+          </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;
+
+    changeVisible('auditNode', false);
+    if (currentNode.id) {
+      let index = FlowNodes.findIndex(item => item.id == currentNode.id);
+      let newNodes = [...FlowNodes];
+      newNodes[index] = values;
+      setCurrent({
+        ...current,
+        FlowNodes: newNodes,
+      });
+    } else {
+      setCurrent({
+        ...current,
+        FlowNodes: [...FlowNodes, values],
+      });
+    }
+  };
+  const handleDelete = index => {
+    let newNodes = [...current.FlowNodes];
+    newNodes.splice(index, 1);
+    setCurrent({
+      ...current,
+      FlowNodes: newNodes,
+    });
+  };
+  const onCancel = () => {
+    form.resetFields();
+    setCurrent({});
+  };
+  const onSave = () => {
+    const nodes = current.FlowNodes.map(item => ({
+      flow_id: current.id,
+      node: item.node,
+      desc: item.desc,
+      auditor: item.auditor,
+      seq: item.seq,
+      seq_relate: item.seq_relate,
+    }));
+
+    dispatch({
+      type: 'flow/addAuditNode',
+      payload: {
+        flowId: current.id,
+        nodes: nodes,
+      },
+    });
+  };
+  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,
+    });
+  };
+
+  useEffect(() => {
+    dispatch({
+      type: 'user/fetch',
+    });
+    dispatch({
+      type: 'flow/queryAuditList',
+    });
+  }, []);
+
+  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>
+    );
+  };
+
+  return (
+    <div>
+      <div className={styles.box}>
+        <Form layout="inline" name="basic" autoComplete="off" form={form}>
+          <Form.Item label="审批流程" name="flowId">
+            <Select onChange={handleSelect} style={{ minWidth: 180 }}>
+              {list.map(item => (
+                <Option key={item.list.id}>{item.list.name || item.list.id}</Option>
+              ))}
+            </Select>
+          </Form.Item>
+        </Form>
+        <Button onClick={() => changeVisible('audit', true)} type="primary">
+          新建流程
+        </Button>
+      </div>
+
+      <Table
+        rowKey="id"
+        title={renderTitle}
+        dataSource={current?.FlowNodes || []}
+        columns={columns}
+      />
+      <AuditModal
+        visible={visible.audit}
+        onOk={handleAuditOk}
+        onCancel={() => changeVisible('audit', false)}
+      />
+      <AuditNodeModal
+        userList={userList}
+        data={currentNode}
+        visible={visible.auditNode}
+        onOk={handleAuditNodeOk}
+        onCancel={() => changeVisible('auditNode', false)}
+      />
+    </div>
+  );
+}
+export default connect(({ user, flow, loading }) => ({
+  userList: user.list,
+  list: flow.auditList,
+  loading: loading.models.purchaseList2,
+}))(Audit);

+ 15 - 0
src/pages/PurchaseAdmin/PurchaseList/Flow/Audit.less

@@ -0,0 +1,15 @@
+.box {
+  margin-bottom: 20;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.btns {
+  display: flex;
+  align-items: center;
+  :global {
+    .ant-btn {
+      margin-left: 20px;
+    }
+  }
+}

+ 40 - 0
src/pages/PurchaseAdmin/PurchaseList/Flow/AuditModal.js

@@ -0,0 +1,40 @@
+import React, { useEffect } from 'react';
+import { Modal, Input, Table, Select, Form, Radio } from 'antd';
+const { Option } = Select;
+
+// 审批意见
+function AuditModal(props) {
+  const { visible, onCancel, onOk, userList = [], data = {}, loading } = props;
+  const [form] = Form.useForm();
+  const formLayout = { labelCol: { span: 4 }, wrapperCol: { span: 14 } };
+
+  const handleOk = async () => {
+    let fieldsValue = await form.validateFields();
+    onOk(fieldsValue);
+  };
+
+  useEffect(() => {
+    if (visible) form.resetFields();
+  }, [visible]);
+  return (
+    <Modal
+      confirmLoading={loading}
+      destroyOnClose
+      title="流程"
+      visible={visible}
+      onCancel={onCancel}
+      onOk={handleOk}
+    >
+      <Form {...formLayout} form={form}>
+        <Form.Item label="流程名称" name="name">
+          <Input />
+        </Form.Item>
+        <Form.Item label="详情" name="desc">
+          <Input.TextArea />
+        </Form.Item>
+      </Form>
+    </Modal>
+  );
+}
+
+export default AuditModal;

+ 72 - 0
src/pages/PurchaseAdmin/PurchaseList/Flow/AuditNodeModal.js

@@ -0,0 +1,72 @@
+import React, { useEffect } from 'react';
+import { Modal, Input, Table, Select, Form, Radio } from 'antd';
+const { Option } = Select;
+
+// 审批意见
+function AuditModal(props) {
+  const { visible, onCancel, onOk, userList = [], data = {}, loading } = props;
+  const [form] = Form.useForm();
+  const formLayout = { labelCol: { span: 4 }, wrapperCol: { span: 14 } };
+
+  const handleOk = async () => {
+    let fieldsValue = await form.validateFields();
+    const { label, value } = fieldsValue.auditUser;
+    fieldsValue.AuditorUser = {
+      CName: label,
+    };
+    fieldsValue.auditor = value;
+    // 如果id不存在则使用时间戳作为id
+    fieldsValue.id = data?.id || new Date() * 1;
+    onOk(fieldsValue);
+  };
+
+  useEffect(() => {
+    if (visible) form.resetFields();
+  }, [visible]);
+
+  return (
+    <Modal
+      confirmLoading={loading}
+      title="审批节点"
+      visible={visible}
+      onCancel={onCancel}
+      onOk={handleOk}
+    >
+      <Form {...formLayout} form={form} initialValues={data}>
+        <Form.Item label="节点名" name="node">
+          <Input />
+        </Form.Item>
+        <Form.Item label="审批级别" name="seq">
+          <Select style={{ width: '100%' }}>
+            <Option value={1}>一级</Option>
+            <Option value={2}>二级</Option>
+            <Option value={3}>三级</Option>
+            {/* <Option value={1}>流程1</Option> */}
+          </Select>
+        </Form.Item>
+        <Form.Item label="审批人" initialValue={data?.auditor} name="auditUser">
+          <Select
+            showSearch
+            labelInValue
+            style={{ width: '100%' }}
+            filterOption={(input, option) => option.props.children.indexOf(input) >= 0}
+          >
+            {userList.map(item => (
+              <Option key={item.ID} value={item.ID}>
+                {item.CName}
+              </Option>
+            ))}
+          </Select>
+        </Form.Item>
+        <Form.Item initialValue={data?.seq_relate || 1} label="审批关系" name="seq_relate">
+          <Radio.Group>
+            <Radio value={1}>并</Radio>
+            <Radio value={2}>或</Radio>
+          </Radio.Group>
+        </Form.Item>
+      </Form>
+    </Modal>
+  );
+}
+
+export default AuditModal;

+ 56 - 7
src/pages/PurchaseAdmin/PurchaseList/Flow/Flow.js

@@ -1,10 +1,59 @@
-
 import Flow, { FLOW_TYPE } from '@/components/Flow';
+import { connect } from 'dva';
+import React, { useEffect } from 'react';
+// import { Form } from 'antd';
 
-export default function IndexPage() {
-  return (
-    <div>
-      <Flow meta={{ type: 'edit' }} />
-    </div>
-  );
+@connect(({ xflow }) => ({ flowDetail: xflow.flowDetail }))
+class FlowPage extends React.PureComponent {
+  onUpdate(node) {
+    const { dispatch, flowDetail } = this.props;
+    console.log(node);
+    if (node.is_start_node && node.bom_template) {
+      let params = {
+        ...node,
+        id: node.Id,
+        node_type: 0,
+        data: JSON.stringify(node.data),
+        project_id: flowDetail.ProjectId,
+        template_id: flowDetail.Id,
+        template_name: flowDetail.Name,
+      };
+      // delete params.label;
+      dispatch({
+        type: 'flow/updateNode',
+        payload: {
+          templateId: flowDetail.Id,
+          nodeId: node.Id,
+          body: params,
+        },
+      });
+    }
+  }
+  componentDidMount() {
+    const { dispatch } = this.props;
+    dispatch({
+      type: 'xflow/queryOSSData',
+    });
+    dispatch({
+      type: 'xflow/queryBoomFlowDetail',
+      payload: {
+        id: 1,
+      },
+    });
+  }
+  render() {
+    const { flowDetail } = this.props;
+    return (
+      <div>
+        {/* <Form></Form> */}
+        <Flow
+          meta={{ type: 'edit', flowId: 1 }}
+          flowDetail={flowDetail}
+          onUpdate={node => this.onUpdate(node)}
+        />
+      </div>
+    );
+  }
 }
+
+export default FlowPage;

+ 36 - 109
src/pages/PurchaseAdmin/PurchaseList/Flow/models/flow.js

@@ -1,133 +1,60 @@
-import { queryUserList } from '@/services/plant';
-import { queryDep } from '@/services/SysAdmin';
-import { serviceGetTemplate, serviceSaveTemplate } from '@/services/flow';
-import { createCorp, commentCorp, deleteCorpFile } from '@/services/issueTicket';
+import {
+  queryAuditList,
+  addAudit,
+  addAuditNode,
+  queryBoomFlowDetail,
+  updateNode,
+} from '@/services/boom';
 import { message } from 'antd';
 
-const depDict = {};
-function getDepMapData(data) {
-  depDict[data.ID] = data;
-  if (data.children) {
-    data.children.forEach(item => {
-      getDepMapData(item, false);
-    });
-  }
-}
-function getDepTreeData(data) {
-  data.title = `${data.Name}`;
-  data.key = data.ID;
-  data.value = data.ID;
-  if (data.children) {
-    data.children.forEach(item => {
-      getDepTreeData(item, false);
-    });
-  }
-  return data;
-}
-function getDepUserTree(data) {
-  data.title = `${data.Name}`;
-  data.key = `dep-${data.ID}`;
-  data.value = `dep-${data.ID}`;
-  data.selectable = false;
-  if (!data.children) data.children = new Array();
-
-  if (data.children) {
-    data.children.forEach(item => {
-      getDepUserTree(item, false);
-    });
-  }
-
-  if (data.Users && data.Users.length !== 0) {
-    data.Users.forEach(item => {
-      item.title = item.CName;
-      item.key = item.ID;
-      item.value = item.ID;
-      item.selectable = true;
-      data.children.push(item);
-    });
-  }
-  return data;
-}
-
 export default {
   namespace: 'flow',
   state: {
-    user: [],
-    depUserTree: [],
-    depTrees: [],
-    template: {
-      nodes: [],
-      edges: [],
-    },
+    flowDetail: { nodes: [], edges: [] },
   },
 
   effects: {
-    *queryUserList({ payload }, { call, put }) {
-      const response = yield call(queryUserList, payload);
+    *queryBoomFlowDetail({ payload }, { call, put }) {
+      const data = yield call(queryBoomFlowDetail, payload);
+      console.log(data);
+      yield put({
+        type: 'save',
+        payload: { flowDetail: data },
+      });
+    },
+    *updateNode({ payload }, { call, put }) {
+      const data = yield call(updateNode, payload);
+      console.log(data);
+      message.success('修改成功');
+    },
+    *queryAuditList({ payload }, { call, put }) {
+      const response = yield call(queryAuditList, payload);
       if (response) {
         yield put({
           type: 'save',
-          payload: { user: response.data },
+          payload: { auditList: response.data },
         });
       }
     },
-    *serviceGetTemplate({ payload }, { call, put }) {
-      const response = yield call(serviceGetTemplate, payload);
+    *addAudit({ payload, callback }, { call, put }) {
+      const response = yield call(addAudit, payload);
       if (response) {
-        if (response.data.nodes) {
-          response.data.nodes.forEach(item => {
-            if (item.team_workers) item.corpList = item.team_workers.split(',');
-          });
-        }
+        message.success('新增成功');
+        callback && callback();
         yield put({
-          type: 'save',
-          payload: { template: response.data },
+          type: 'queryAuditList',
+          payload: {},
         });
       }
     },
-    *serviceSaveTemplate({ payload }, { call, put }) {
-      const response = yield call(serviceSaveTemplate, payload);
-      message.success('保存成功');
-    },
-    *createCorp({ payload }, { call, put }) {
-      const response = yield call(createCorp, payload);
-      if (response) {
-        message.success('请求协同成功');
-      }
-    },
-    *commentCorp({ payload }, { call, put }) {
-      const response = yield call(commentCorp, payload);
-      if (response) {
-        message.success('消息发送成功');
-      }
-    },
-    *deleteCorpFile({ payload }, { call, put }) {
-      const response = yield call(deleteCorpFile, payload);
-      if (response) {
-        message.success('文件删除成功');
-      }
-    },
-    *queryDep({ payload }, { call, put }) {
-      const response = yield call(queryDep, { pageSize: 999999 });
+    *addAuditNode({ payload, callback }, { call, put }) {
+      const response = yield call(addAuditNode, payload);
       if (response) {
-        var depTrees, depUserTree;
-        try {
-          depTrees = response.data.list.map(item => {
-            getDepMapData(item);
-            return getDepTreeData(item);
-          });
-
-          depUserTree = response.data.list.map(item => {
-            return getDepUserTree(item);
-          });
-          console.log(depTrees, depUserTree);
-        } catch (e) {
-          console.error(e);
-        }
-
+        message.success('新增成功');
+        callback && callback();
         yield put({
-          type: 'save',
-          payload: { depTrees, depUserTree },
+          type: 'queryAuditList',
+          payload: {},
         });
       }
     },

+ 71 - 0
src/pages/PurchaseAdmin/PurchaseList/List/NewList.js

@@ -0,0 +1,71 @@
+import React, { useState, useEffect } from 'react';
+import { Table, Divider } from 'antd';
+import { connect } from 'dva';
+import router from 'umi/router';
+
+function List(props) {
+  const { excel, loading, dispatch } = props;
+
+  const columns = [
+    {
+      title: '名称',
+      dataIndex: 'name',
+    },
+    // {
+    //   title: '所属项目',
+    //   dataIndex: 'project_id',
+    //   render: id => {
+    //     return project.list.find(item => item.ID == id)?.Name;
+    //   },
+    // },
+    {
+      title: '当前节点',
+      dataIndex: 'NodeInfo.node',
+    },
+    {
+      title: '操作',
+      render: record => (
+        <>
+          <a onClick={() => router.push(`/home/detail/${record.id}/${record.project_id}`)}>查看</a>
+        </>
+      ),
+    },
+  ];
+  const queryList = page => {
+    console.log(page);
+    dispatch({
+      type: 'newList/queryProjectRecord',
+      payload: {
+        ...page,
+        currentPage: page.current,
+      },
+    });
+  };
+
+  useEffect(() => {
+    dispatch({
+      type: 'newList/queryProjectRecord',
+      payload: {
+        pageSize: 20,
+      },
+    });
+  }, []);
+
+  return (
+    <div>
+      <Table
+        loading={loading}
+        rowKey="id"
+        dataSource={excel.list}
+        pagination={excel.pagination}
+        columns={columns}
+        onChange={queryList}
+      />
+    </div>
+  );
+}
+
+export default connect(({ newList, loading }) => ({
+  excel: newList.excel,
+  loading: loading.models.newList,
+}))(List);

+ 36 - 0
src/pages/PurchaseAdmin/PurchaseList/List/models/newList.js

@@ -0,0 +1,36 @@
+import { queryProjectRecord } from '@/services/boom';
+
+import { message } from 'antd';
+
+export default {
+  namespace: 'newList',
+  state: {
+    excel: {
+      list: [],
+      pagination: {},
+    },
+  },
+
+  effects: {
+    *queryProjectRecord({ payload = {}, callback }, { call, put }) {
+      const response = yield call(queryProjectRecord, payload);
+      if (response) {
+        console.log(response);
+        const flow = response.data.list[0];
+        yield put({
+          type: 'save',
+          payload: { excel: response.data },
+        });
+      }
+    },
+  },
+
+  reducers: {
+    save(state, action) {
+      return {
+        ...state,
+        ...action.payload,
+      };
+    },
+  },
+};

+ 202 - 0
src/services/boom.js

@@ -0,0 +1,202 @@
+import request from '@/utils/request';
+import { stringify } from 'qs';
+
+/**
+  project_id
+  version_id	大版本id
+  template_id	
+  template_node_id	查询某流程和某节点下最新版本的数据记录
+  node_id	查询某审批流程和某审批节点下最新版本的数据记录
+ */
+export async function queryRecord(params) {
+  return request(`/purchase/record?${stringify(params)}`);
+}
+
+export async function commitSheet(params) {
+  return request(`/purchase/record`, {
+    method: 'POST',
+    body: params
+  });
+}
+
+export async function queryDetail(params) {
+  return request(`/purchase/record?${stringify(params)}`);
+}
+export async function queryHistoryDetail(params) {
+  return request(`/purchase/record/history?${stringify(params)}`);
+}
+
+export async function queryBoomFlowList(params) {
+  return request(`/purchase/bom/flows?${stringify(params)}`);
+}
+/**
+ * 查看项目流程列表
+ * project_id 
+ */
+export async function queryProjectRecord(params) {
+  return request(`/purchase/bom/project/record?${stringify(params)}`);
+}
+/** 查看版本列表
+ *  project_id		
+    template_id		流程id
+    template_node_id	流程节点id
+ */
+export async function queryVersionsList(params) {
+  return request(`/purchase/record/versions?${stringify(params)}`);
+}
+
+export async function queryBoomFlowDetail(params) {
+  let { data } = await request(`/purchase/bom/flow/info?${stringify(params)}`);
+  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 = {
+      Id: item.Id,
+      id: item.node_id,
+      name: item.name,
+      renderKey: item.render_key,
+      color: item.color,
+      height: item.height,
+      label: item.label,
+      width: item.width,
+      x: item.x,
+      y: item.y,
+      zIndex: item.z_index,
+      isCustom: !!item.is_custom,
+      ports: JSON.parse(item.ports || '{}'),
+    };
+    node.ports.groups = groups;
+    node.parentKey = '1';
+
+    return node;
+  });
+  let edges = data.Edges.map(item => {
+    let edge = {
+      id: item.edge_id,
+      source: {
+        cell: item.source_cell,
+        port: item.source_port,
+      },
+      target: {
+        cell: item.target_cell,
+        port: item.target_port,
+      },
+    };
+    edge.attrs = attrs;
+    return edge;
+  });
+  return {
+    ...data,
+    nodes,
+    edges,
+  };
+}
+export async function updateNode(data) {
+  return request(`/purchase/bom/flow/${data.templateId}/${data.nodeId}`, {
+    method: 'PUT',
+    body: data.body,
+  });
+}
+export async function addBoomFlow(data) {
+  return request(`/purchase/bom/flow/info`, {
+    method: 'POST',
+    body: data,
+  });
+}
+
+export async function queryAuditList(params) {
+  return request(`/purchase/flow/info?${stringify(params)}`);
+}
+
+export async function addAudit(data) {
+  return request(`/purchase/flow/info`, {
+    method: 'POST',
+    body: data,
+  });
+}
+/**
+ *  [
+      {
+        "flow_id": 23,
+        "node": "主管",
+        "desc": "desc",
+        "auditor": 2,
+        "seq": 1,
+        "seq_relate": 0
+      }
+    ]
+ */
+export async function addAuditNode(data) {
+  return request(`/purchase/flow/info/${data.flowId}`, {
+    method: 'POST',
+    body: data.nodes,
+  });
+}
+
+export async function queryOSSData() {
+  return request(`/config/chart-template-img?destDir=public/bom`);
+}

+ 3 - 1
src/utils/request.js

@@ -41,7 +41,9 @@ const checkStatus = response => {
   const error = new Error(response.data);
   error.name = response.status;
   error.response = response;
-  throw error;
+  console.error(error)
+  // throw error;
+  return Promise.reject()
 };
 
 const cachedSave = response => {