Explorar el Código

Merge branch 'develop'

xujunjie hace 1 año
padre
commit
6554f56abc

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
public/Luckysheet/luckysheet.umd.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
public/Luckysheet/luckysheet.umd.js.map


+ 1 - 0
src/components/Flow/node/rect/mapServe.tsx

@@ -241,6 +241,7 @@ const Component = (props: any) => {
             { label: '签字版', value: 2 },
             { label: '投标测算', value: 3 },
             { label: '合同测算', value: 4 },
+            { label: '采购合同', value: 5 },
           ]}
         />
         {nodeConfig.is_start_node == 1 && (

+ 67 - 8
src/pages/Detail/AuditModal.js

@@ -1,22 +1,58 @@
-import React, { useEffect, useMemo } from 'react';
+import React, { useEffect, useMemo, useRef, useState } from 'react';
 import { Form } from '@ant-design/compatible';
 import '@ant-design/compatible/assets/index.css';
 import { Modal, Input } from 'antd';
+import AliyunOSSUpload from '@/components/OssUpload/AliyunOssUploader';
+import { connect } from 'dva';
 
 // 审批意见
 function AuditModal(props) {
-  const { flow, version, flowDetail, visible, onClose, onOk, form, sheetRef, loading, versionList } = props;
+  const {
+    flow,
+    version,
+    flowDetail,
+    visible, // 1 通过审批   2 拒绝  3 退回
+    onClose,
+    onOk,
+    form,
+    sheetRef,
+    loading,
+    versionList,
+    OSSData,
+    dispatch,
+  } = props;
+
+  const filesRef = useRef();
 
   const handleOk = () => {
     form.validateFields((err, fieldsValue) => {
       if (err) return;
+      let audit_status = null;
+      // 3 通过审批   2 拒绝审批  5 退回
+      if (visible == 1) {
+        audit_status = 3;
+      } else if (visible == 2) {
+        audit_status = 2;
+      } else if (visible == 3) {
+        audit_status = 5;
+      }
       onOk({
         ...fieldsValue,
-        // 3 通过审批   2 拒绝审批
-        audit_status: visible == 1 ? 3 : 2,
+        audit_status,
+        files: filesRef.current,
       });
     });
   };
+  const uploadProps = {
+    directory: false,
+    label: '上传文件',
+    OSSData: OSSData,
+    noStyle: false,
+    onChange: files => {
+      console.log(files)
+      filesRef.current = files.map(file => OSSData.host + '/' + file.url).join(',');
+    },
+  };
 
   const content = useMemo(() => {
     let content = '';
@@ -64,8 +100,25 @@ function AuditModal(props) {
     return '';
   }, [visible]);
 
+  const title = useMemo(() => {
+    switch (visible) {
+      case 1:
+        return '是否确认通过审批?';
+      case 2:
+        return '是否确认拒绝审批?';
+      case 3:
+        return '是否确认回退审批?';
+    }
+  }, [visible]);
+
   useEffect(() => {
     if (visible) {
+      dispatch({
+        type: 'detail/getChartOSSData',
+        payload: {
+          projectId: version.project_id,
+        },
+      });
       try {
         const comment = sheetRef.current.getComment();
         console.log(comment);
@@ -75,7 +128,9 @@ function AuditModal(props) {
           str += `单元格${col}${item.r}:${item.value}\n`;
         });
         form.setFieldsValue({ audit_comment: str });
-      } catch (error) { }
+      } catch (error) {}
+    } else {
+      filesRef.current = '';
     }
   }, [visible]);
 
@@ -83,7 +138,7 @@ function AuditModal(props) {
     <Modal
       confirmLoading={loading}
       destroyOnClose
-      title={visible == 1 ? '是否确认通过审批?' : '是否确认拒绝审批?'}
+      title={title}
       visible={visible}
       onCancel={onClose}
       onOk={handleOk}
@@ -92,8 +147,12 @@ function AuditModal(props) {
       <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="审批意见">
         {form.getFieldDecorator('audit_comment')(<Input.TextArea />)}
       </Form.Item>
+      <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="附件">
+        {OSSData.host && <AliyunOSSUpload {...uploadProps} />}
+      </Form.Item>
     </Modal>
   );
 }
-
-export default Form.create()(AuditModal);
+export default connect(({ detail }) => ({
+  OSSData: detail.OSSData,
+}))(Form.create()(AuditModal));

+ 15 - 46
src/pages/Detail/CommitAuditModal.js

@@ -1,4 +1,4 @@
-import React, {useEffect, useState, useRef, useMemo, useCallback} from 'react';
+import React, { useEffect, useState, useRef, useMemo, useCallback } from 'react';
 import '@ant-design/compatible/assets/index.css';
 import {
   Modal,
@@ -43,6 +43,7 @@ import AttachmentTable from '@/components/AttachmentTable';
 import {getToken} from '@/utils/utils';
 import LuckyExcel from 'luckyexcel';
 import DDComponents from "@/components/DDComponents";
+import uploadExcelByUrl from '@/utils/uploadExcelByUrl';
 
 const {TextArea} = Input;
 const {Option} = Select;
@@ -63,6 +64,7 @@ function CommitAuditModal(props) {
     luckysheet,
     userList,
     templateId,
+    projectList,
     OSSData,
   } = props;
   // console.log(loading);
@@ -158,6 +160,8 @@ function CommitAuditModal(props) {
     if (res.data) {
       const formList = JSON.parse(res.data.json);
       setApprovalProcess(formList.approvalProcess || {});
+      const prevFormData = JSON.parse(formList.formList?.[0] || '{}');
+      setFormComponentValues(prevFormData);
       return formList;
     }
   };
@@ -543,7 +547,10 @@ function CommitAuditModal(props) {
         next_template_id: version.template_id,
       };
       if (serviceNode.node_type_psr == 3 || serviceNode.node_type_psr == 4) {
-        params.data = await uploadExcelByUrl(serviceNode.node_type_psr, version.id);
+        let project = projectList.find(item => item.id == version?.project_id);
+        let projectName = project?.project_name || '';
+        let sheetData = await uploadExcelByUrl(serviceNode.node_type_psr, version.id, projectName);
+        params.data = JSON.stringify(sheetData);
       }
       // params.data = await uploadExcelByUrl(3, version.id);
       console.log(params);
@@ -578,6 +585,9 @@ function CommitAuditModal(props) {
         json: JSON.stringify({approvalProcess, formList}),
       });
       params.audit_series = uuidv4();
+      if(version.audit_status == 5) {
+        params.audit_status = version.audit_status;
+      }
       params.files = uploadList.current.join(',');
       onSubmitNextNode(params);
     } catch (error) {
@@ -730,53 +740,12 @@ function getDataValue(item) {
   return arr;
 }
 
-const uploadExcelByUrl = (nodeType, versionId) => {
-  const TEMPLATE_URL =
-    'https://water-service-test.oss-cn-hangzhou.aliyuncs.com/doc/contract/2023-06-29/ed0d5dcd-6ce0-40df-9d17-a1f69245dbb9.xlsx';
-  const TEMPLATE_URL2 =
-    'https://water-service-test.oss-cn-hangzhou.aliyuncs.com/doc/contract/2023-06-29/431733cd-0abc-4a68-a439-d24c466e9845.xlsx';
-
-  return new Promise((reslove, reject) => {
-    LuckyExcel.transformExcelToLuckyByUrl(
-      nodeType == 3 ? TEMPLATE_URL : TEMPLATE_URL2,
-      '模板.xlsx',
-      async (exportJson, luckysheetfile) => {
-        let [record] = await getExcel(versionId);
-
-        let len = exportJson.sheets.length;
-        const excelData = exportJson.sheets?.map(item => {
-          return {...item, order: Number(item.order)};
-        });
-        delete record.id;
-        record.order = len;
-        record.index = String(len);
-        record.status = '0';
-        record.name = '投标成本';
-        var res = [...excelData, record];
-        console.log(res);
-        reslove(JSON.stringify(res));
-      }
-    );
-  });
-};
-
-async function getExcel(gridKey) {
-  var formData = new FormData();
-  formData.append('gridKey', gridKey);
-  let res = await fetch(
-    `/api/v1/purchase/record/sheet?gridKey=${gridKey}&JWT-TOKEN=${getToken()}`,
-    {
-      method: 'POST',
-      body: formData,
-    }
-  ).then(response => response.text());
-  return JSON.parse(JSON.parse(res));
-}
-
-export default connect(({xflow, detail, user}) => ({
+export default connect(({ xflow, detail, user, list }) => ({
   flowDetail: xflow.flowDetail,
   versionList: detail.versionList,
   currentUser: user.currentUser,
   userList: user.list,
   OSSData: detail.OSSData,
+  // 隐患:刷新页面后将会丢失projectList
+  projectList: list?.project?.list || [],
 }))(CommitAuditModal);

+ 0 - 1
src/pages/Detail/CurrentInfo.js

@@ -7,7 +7,6 @@ function CurrentInfo(props) {
   const nodeId = version.template_node_id;
   if (!flowDetail?.nodes || !nodeId) return null;
   const node = flowDetail.nodes.find(item => item.Id == nodeId);
-  console.log(projectList);
 
   const projectName = useMemo(() => {
     let project = projectList.find(item => item.id == version?.project_id);

+ 1 - 1
src/pages/Detail/DropdownMenu.js

@@ -153,7 +153,7 @@ function DropdownMenu(props) {
       }
       return bool;
     };
-    if (getIsSubmit() && version.audit_status == 0)
+    if (getIsSubmit() && (version.audit_status == 0 || version.audit_status == 5))
       menuList.push(<Menu.Item key="commitAudit">提交流转</Menu.Item>);
 
     if (!isAuditor && canEdit() && !version.flow_id) {

+ 24 - 1
src/pages/Detail/FlowModal.js

@@ -290,6 +290,9 @@ function FlowModal(props) {
             case 4:
               txt = '已提交';
               break;
+            case 5:
+              txt = '已退回';
+              break;
           }
           if (item.status == 1) txt = '已失效';
 
@@ -400,6 +403,14 @@ function FlowModal(props) {
 
   const getDescription = (node, prevNode) => {
     let str = `审批人:${node.AuditorUser?.CName || '-'}`;
+    let filesList = [];
+    if (node.files) {
+      filesList = node.files.split(',').map(item => {
+        const list = item.split('/');
+        const name = list[list.length - 1];
+        return { name, url: item };
+      });
+    }
     const date = new Date(node.audit_time);
     const auditTime =
       node.audit_time === '0001-01-01T00:00:00Z'
@@ -420,6 +431,18 @@ function FlowModal(props) {
         <div>
           <span>审批时间:{auditTime || '-'}</span>
         </div>
+        {filesList.length > 0 && (
+          <div style={{ display: 'flex' }}>
+            附件:
+            <div>
+              {filesList.map(item => (
+                <a target="_blank" href={item.url}>
+                  {item.name}
+                </a>
+              ))}
+            </div>
+          </div>
+        )}
         {/* <div> */}
         {/*   <span>滞留时间:{`${residenceTime}小时`}</span> */}
         {/* </div> */}
@@ -611,7 +634,7 @@ function FlowModal(props) {
                       current={item.current}
                       status={item.status}
                     >
-                      {item.list.map(( node) => (
+                      {item.list.map(node => (
                         <Step
                           key={`${node.id}_${node.node}`}
                           title={node.node}

+ 16 - 10
src/pages/Detail/Index.js

@@ -7,13 +7,10 @@ import LuckySheet from './LuckySheet';
 import AuditModal from './AuditModal';
 // import CommentDrawer from './CommentDrawer';
 import RightDrawer from './RightDrawer';
-import CommitModal from './CommitModal';
 import CompareModal from './CompareModal';
 import ExportModal from './ExportModal';
 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 CommentContent from '@/components/CommentContent';
@@ -36,9 +33,7 @@ import FormAndFilesNode from './FormAndFilesNode';
 import DropdownMenu from './DropdownMenu';
 import CurrentInfo from './CurrentInfo';
 import moment from 'moment';
-import { LocalStorage } from '@antv/x6';
-
-const LocalData = localStorage.luckysheet;
+import PsrControl from './PsrControl';
 
 function Detail(props) {
   const {
@@ -273,7 +268,7 @@ function Detail(props) {
     await queryDelSheetRecord(params);
   };
 
-  const onAudit = ({ audit_comment, audit_status }) => {
+  const onAudit = (data) => {
     const flowNode = flow.currentNode;
     dispatch({
       type: 'detail/approve',
@@ -282,8 +277,7 @@ function Detail(props) {
         project_id: projectId,
         flow_id: flowNode.flow_id,
         node_id: flowNode.seq,
-        audit_comment,
-        audit_status,
+        ...data
       },
       callback: newVersion => {
         setAuditVisible(false);
@@ -294,7 +288,7 @@ function Detail(props) {
             id: templateId,
           },
         });
-        if (audit_status == 3) {
+        if (data.audit_status == 3) {
           // 更新审批流
           dispatch({
             type: 'detail/queryAuditList',
@@ -442,6 +436,16 @@ function Detail(props) {
     }
   };
 
+  // console.log('0--------------', currentUser);
+
+  //是否展示psr表上面的按钮
+  const showPsrBtns = useMemo(() => {
+    let Id = version.template_node_id;
+    const node = flowDetail.nodes.find?.(item => item.Id == Id);
+    if (node?.node_type_psr >= 1 && node?.node_type_psr <= 4) return true;
+    return false;
+  }, [flowDetail, version]);
+
   const getUser = newUser => {
     try {
       if (JSON.stringify(newUser) != JSON.stringify(userRef.current)) {
@@ -589,6 +593,7 @@ function Detail(props) {
           onChange={e => exportExcl(e.target.files)}
         />
       </div>
+      {showPsrBtns && <PsrControl sheetRef={sheetRef} />}
       <div style={{ display: 'flex' }}>
         <div
           className={styles.content}
@@ -614,6 +619,7 @@ function Detail(props) {
                   className={styles.sheet}
                   ref={sheetRef}
                   onClickCell={onClickCell}
+                  permissions={currentUser.Permission}
                   version={version}
                   templateId={templateId}
                   getUser={getUser}

+ 60 - 3
src/pages/Detail/LuckySheet.js

@@ -1,8 +1,9 @@
 import React from 'react';
-import { message } from 'antd';
+import { Button, message } from 'antd';
 import exportExcel, { getExcelBolob } from '@/utils/exportExcl';
 import LuckyExcel from 'luckyexcel';
 import { getToken, GetTokenFromUrl } from '@/utils/utils';
+import GoalSeek from '@/utils/GoalSeek';
 
 const hintText = '禁止编辑!请先点击编辑按钮。';
 const DIFF_COLOR = '#ff0000';
@@ -64,7 +65,15 @@ class LuckySheet extends React.Component {
     return uuid.join('');
   }
   renderSheet(currentData) {
-    const { onClickCell, version, getUser, onUpdate, templateId, onDelSheet } = this.props;
+    const {
+      onClickCell,
+      version,
+      getUser,
+      onUpdate,
+      templateId,
+      onDelSheet,
+      permissions,
+    } = this.props;
     const data = currentData || this.props.data;
     const _this = this;
     if (!this.luckysheet) {
@@ -82,6 +91,7 @@ class LuckySheet extends React.Component {
       // forceCalculation: true,
       hook: {
         cellMousedown: (cell, position, sheet) => {
+          console.log(cell, position, sheet);
           onClickCell && onClickCell(cell, position, sheet);
         },
         cellPasteBefore: cell => {
@@ -149,6 +159,7 @@ class LuckySheet extends React.Component {
         authorityUrl: `/api/v1/purchase/bom/user/excel/col?depId=${localStorage.depId ||
           0}&JWT-TOKEN=${token}`,
         getUser,
+        permissions,
         // workbookCreateBefore(luckysheet) {
         //   console.log('===============================', luckysheet);
         //   let oldConfig = JSON.parse(JSON.stringify(luckysheet.getConfig()));
@@ -208,7 +219,7 @@ class LuckySheet extends React.Component {
           hintText: '该清单已设置为最终版本,禁止编辑!',
         };
         unableEdit(option);
-      } else if (version.audit_status != 0 || version.status == 1) {
+      } else if ((version.audit_status != 0 && version.audit_status != 5) || version.status == 1) {
         option.authority = {
           sheet: true,
           hintText: '当前清单不可编辑!',
@@ -614,6 +625,52 @@ class LuckySheet extends React.Component {
     return comment;
   }
 
+  async goalSeek(type, goal,order = 2) {
+    let luckysheet = this.luckysheet;
+
+    const fn = function(x) {
+      return new Promise(resolve => {
+        luckysheet.setCellValue(9, 2, x.toFixed(4), {
+          order,
+          success: () => {
+            luckysheet.refreshFormula(() => {
+              let row;
+              if (type == 1) {
+                row = 1;
+              } else if (type == 2) {
+                row = 4;
+              } else {
+                row = 5;
+              }
+              let data = luckysheet.getCellValue(row, 2, {
+                order,
+              });
+              console.log(data);
+              resolve(data);
+            });
+          },
+        });
+      });
+    };
+    try {
+      let defaultValue = luckysheet.getCellValue(9, 2, {
+        order,
+      });
+      const result = await GoalSeek({
+        goal,
+        fn,
+        fnParams: [defaultValue],
+        maxIterations: 1000,
+        maxStep: 0.03,
+        percentTolerance: 1,
+        independentVariableIdx: 0,
+      });
+      console.log(result);
+    } catch (error) {
+      console.log(error);
+    }
+  }
+
   render() {
     return (
       <iframe

+ 79 - 0
src/pages/Detail/PsrControl.js

@@ -0,0 +1,79 @@
+import { Button, Input, InputNumber, Select, Spin } from 'antd';
+import React, { useState } from 'react';
+
+const { Option } = Select;
+
+function PsrControl(props) {
+  const { sheetRef } = props;
+  const [value1, setValue1] = useState(0.15);
+  const [value2, setValue2] = useState(0.25);
+  const [value3, setValue3] = useState(14096800);
+  const [loading, setLoading] = useState(false);
+
+  const changeProjectType = type => {
+    sheetRef.current.luckysheet.setCellValue(101, 1, type, {
+      order: 0,
+    });
+    sheetRef.current.luckysheet.setCellFormat(101, 1, 'ct', { fa: 'General', t: 'g' });
+    sheetRef.current.luckysheet.refreshFormula();
+  };
+  const changeBiddingType = type => {
+    sheetRef.current.luckysheet.setCellValue(102, 1, type, {
+      order: 0,
+    });
+    sheetRef.current.luckysheet.setCellFormat(102, 1, 'ct', { fa: 'General', t: 'g' });
+    sheetRef.current.luckysheet.refreshFormula();
+  };
+
+  const goalSeek = (type, value) => {
+    setLoading(true);
+    try {
+      sheetRef.current.goalSeek(type, value);
+    } catch (error) {}
+    setLoading(false);
+  };
+
+  return (
+    <div style={{ marginBottom: 20 }}>
+      <Spin spinning={loading}>
+        <Input
+          value={value1}
+          style={{ width: 160, marginRight: 20 }}
+          onChange={e => setValue1(e.target.value)}
+          addonAfter={<a onClick={() => goalSeek(1, value1)}>净利率</a>}
+        />
+        <Input
+          value={value2}
+          style={{ width: 160, marginRight: 20 }}
+          onChange={e => setValue2(e.target.value)}
+          addonAfter={<a onClick={() => goalSeek(2, value2)}>毛利率</a>}
+        />
+        <Input
+          value={value3}
+          style={{ width: 220, marginRight: 20 }}
+          onChange={e => setValue3(e.target.value)}
+          addonAfter={<a onClick={() => goalSeek(3, value3)}>合同总价</a>}
+        />
+
+        <Select
+          placeholder="项目类别"
+          onChange={changeProjectType}
+          style={{ width: 120, marginRight: 20 }}
+        >
+          <Option value="UF">UF</Option>
+          <Option value="RO/NF">RO/NF</Option>
+          <Option value="UF&RO/NF">UF+RO/NF</Option>
+          <Option value="MBR">MBR</Option>
+          <Option value="其他">其他</Option>
+        </Select>
+        <Select placeholder="招标类型" onChange={changeBiddingType} style={{ width: 120 }}>
+          <Option value="货物招标">货物招标</Option>
+          <Option value="服务招标">服务招标</Option>
+          <Option value="工程招标">工程招标</Option>
+        </Select>
+      </Spin>
+    </div>
+  );
+}
+
+export default PsrControl;

+ 30 - 22
src/pages/Detail/TimeNode.js

@@ -1,12 +1,12 @@
-import React, {useEffect, useState, useRef} from 'react';
-import {Form} from '@ant-design/compatible';
+import React, { useEffect, useState, useRef, useMemo } from 'react';
+import { Form } from '@ant-design/compatible';
 import '@ant-design/compatible/assets/index.css';
-import {connect} from 'dva';
-import {Steps, Button, Modal, Tooltip} from 'antd';
+import { connect } from 'dva';
+import { Steps, Button, Modal, Tooltip } from 'antd';
 import styles from './Index.less';
-import {getCurrentUser} from '@/utils/authority';
+import { getCurrentUser } from '@/utils/authority';
 
-const {Step} = Steps;
+const { Step } = Steps;
 
 // 时间节点
 function TimeNode(props) {
@@ -24,9 +24,15 @@ function TimeNode(props) {
     stepDirection,
     currentUser,
   } = props;
-  const {current, list, active} = flow;
-  console.log(list.FlowNodes)
+  const { current, list, active } = flow;
+  const nodeId = version.template_node_id;
 
+  const showBackBtn = useMemo(() => {
+    if (!nodeId || flowDetail.nodes.length == 0) return false;
+    const node = flowDetail.nodes.find(item => item.Id == nodeId);
+    if (node.label == '三级审批1') return true;
+    return false;
+  }, [nodeId, flowDetail]);
 
   function calculateHoursDifference(date1, date2) {
     const timestamp1 = date1.getTime(); // 获取第一个Date对象的时间戳(以毫秒为单位)
@@ -42,23 +48,24 @@ function TimeNode(props) {
     let str = node?.AuditRoleInfo
       ? `审批人:${node?.AuditRoleInfo.Name || '-'}`
       : `审批人:${node?.AuditorUser.CName || '-'}`;
-    const date = new Date(node.audit_time)
-    const auditTime = node.audit_time === '0001-01-01T00:00:00Z' ? '-' : date.toLocaleDateString('zh-CN', {
-      format: 'YYYY-MM-DD hh:mm:ss'
-    })
+    const date = new Date(node.audit_time);
+    const auditTime =
+      node.audit_time === '0001-01-01T00:00:00Z'
+        ? '-'
+        : date.toLocaleDateString('zh-CN', {
+            format: 'YYYY-MM-DD hh:mm:ss',
+          });
     // const residenceTime = auditTime === '-' ? '-' : calculateHoursDifference(date, new Date(prevNode.audit_time))
     return (
       <div>
         {str}
         <div>
-          <span style={{color: '#1A73E8', textDecoration: 'undeline'}}>
+          <span style={{ color: '#1A73E8', textDecoration: 'undeline' }}>
             审批意见:{node.desc || '-'}
           </span>
         </div>
         <div>
-          <span>
-            审批时间:{auditTime}
-          </span>
+          <span>审批时间:{auditTime}</span>
         </div>
         {/* <div> */}
         {/*   <span> */}
@@ -79,17 +86,18 @@ function TimeNode(props) {
           current={current}
           status={active == 0 ? 'error' : 'process'}
         >
-          {list.FlowNodes.map(( item) => {
-            return <Step key={item.id} title={item.node} description={getDescription(item)} />
+          {list.FlowNodes.map(item => {
+            return <Step key={item.id} title={item.node} description={getDescription(item)} />;
           })}
         </Steps>
         {isAuditor && active != 0 && (
-          <div className={styles.btns} style={{margin: '40px 0'}}>
+          <div className={styles.btns} style={{ margin: '40px 0' }}>
             <Button type="primary" onClick={() => setAuditVisible(1)}>
-              审批通过
+              通过
             </Button>
+            <Button onClick={() => setAuditVisible(3)}>回退</Button>
             <Button onClick={() => setAuditVisible(2)} danger>
-              审批拒绝
+              拒绝
             </Button>
           </div>
         )}
@@ -104,7 +112,7 @@ function TimeNode(props) {
   return null;
 }
 
-export default connect(({user, detail}) => ({
+export default connect(({ user, detail }) => ({
   currentUser: user.currentUser,
   versionList: detail.versionList,
 }))(TimeNode);

+ 24 - 65
src/pages/Temp/index.js

@@ -5,52 +5,37 @@ import LuckyExcel from 'luckyexcel';
 import { Button, message } from 'antd';
 import { getToken } from '@/utils/utils';
 import moment from 'moment';
+import uploadExcelByUrl from '@/utils/uploadExcelByUrl';
 
 const TEMPLATE_URL =
-  'https://water-service-test.oss-cn-hangzhou.aliyuncs.com/bom/635/%E5%90%88%E5%90%8C%E6%96%87%E4%BB%B6/%E6%8A%95%E6%A0%87%E6%A8%A1%E6%9D%BF.xlsx';
+  'https://water-service-test.oss-cn-hangzhou.aliyuncs.com/doc/contract/2023-06-29/ed0d5dcd-6ce0-40df-9d17-a1f69245dbb9.xlsx';
 
-const TEMPLATE_URL2 = 'https://water-service-test.oss-cn-hangzhou.aliyuncs.com/public/bom/psr.xlsx';
+const TEMPLATE_URL2 =
+  'https://water-service-test.oss-cn-hangzhou.aliyuncs.com/public/bom/ContractTemplate.xlsx';
 
 function Index(props) {
-  const { versionId = 2376 } = props;
+  const { versionId = 2554 } = props;
 
   const sheetRef = useRef();
   const luckysheetRef = useRef();
 
-  const uploadExcelByUrl = type => {
-    LuckyExcel.transformExcelToLuckyByUrl(
-      type == 1 ? TEMPLATE_URL : TEMPLATE_URL2,
-      '模板.xlsx',
-      async (exportJson, luckysheetfile) => {
-        // if (type == 2) initData(exportJson.sheets);
-        let [record] = await getExcel(versionId);
-
-        let len = exportJson.sheets.length;
-        record.order = len - 1;
-        record.index = len;
-        record.status = '0';
-
-        const data = [...exportJson.sheets, record];
-        console.log(data);
-
-        luckysheetRef.current.destroy();
-
-        luckysheetRef.current.create({
-          data,
-          lang: 'zh',
-          showinfobar: false,
-          showstatisticBar: false,
-          hook: {
-            cellMousedown: (cell, position, sheet) => {
-              console.log(cell, position, sheet);
-            },
-            cellUpdated: () => {
-              luckysheetRef.current.refreshFormula();
-            },
-          },
-        });
-      }
-    );
+  const onClick = async type => {
+    let data = await uploadExcelByUrl(type, versionId, 'test水厂');
+    luckysheetRef.current.destroy();
+    luckysheetRef.current.create({
+      data,
+      lang: 'zh',
+      showinfobar: false,
+      showstatisticBar: false,
+      hook: {
+        cellMousedown: (cell, position, sheet) => {
+          console.log(cell, position, sheet);
+        },
+        cellUpdated: () => {
+          luckysheetRef.current.refreshFormula();
+        },
+      },
+    });
   };
 
   const handleLoad = () => {
@@ -58,25 +43,12 @@ function Index(props) {
     luckysheetRef.current = contentWindow.luckysheet;
   };
 
-  const initData = sheets => {
-    let r = 5,
-      c = 12;
-    // let dateCell = sheets[3].celldata.find(item => item.r == 1 && item.c == 3);
-    let timer = sheets[3].celldata.filter(item => item.r == r && item.c >= c);
-    timer.forEach((item, index) => {
-      const cell = item.v;
-      
-      cell.f = `=EDATE(D2,${index})`;
-    });
-    console.log(timer);
-  };
-
   return (
     <div>
-      <Button type="primary" style={{ marginRight: 20 }} onClick={() => uploadExcelByUrl(1)}>
+      <Button type="primary" style={{ marginRight: 20 }} onClick={() => onClick(3)}>
         导入投标投标
       </Button>
-      <Button type="primary" onClick={() => uploadExcelByUrl(2)}>
+      <Button type="primary" onClick={() => onClick(4)}>
         导入合同模板
       </Button>
 
@@ -93,17 +65,4 @@ function Index(props) {
   );
 }
 
-async function getExcel(gridKey) {
-  var formData = new FormData();
-  formData.append('gridKey', gridKey);
-  let res = await fetch(
-    `/api/v1/purchase/record/sheet?gridKey=${gridKey}&JWT-TOKEN=${getToken()}`,
-    {
-      method: 'POST',
-      body: formData,
-    }
-  ).then(response => response.text());
-  return JSON.parse(JSON.parse(res));
-}
-
 export default Index;

+ 91 - 0
src/utils/GoalSeek.js

@@ -0,0 +1,91 @@
+const IsNanError = TypeError('resulted in NaN');
+const FailedToConvergeError = Error('failed to converge');
+const InvalidInputsError = Error('invalid inputs');
+
+export default async function GoalSeek({
+  fn,
+  fnParams,
+  percentTolerance,
+  customToleranceFn,
+  maxIterations,
+  maxStep,
+  goal,
+  independentVariableIdx,
+}) {
+  if (typeof customToleranceFn !== 'function') {
+    if (!percentTolerance) {
+      throw InvalidInputsError;
+    }
+  }
+  let g;
+  let y;
+  let y1;
+  let oldGuess;
+  let newGuess;
+  let res;
+  const absoluteTolerance = ((percentTolerance || 0) / 100) * goal;
+  // iterate through the guesses
+  for (let i = 0; i < maxIterations; i++) {
+    // define the root of the function as the error
+    res = await fn(...fnParams);
+    y = res - goal;
+    if (isNaN(y)) throw IsNanError;
+    // was our initial guess a good one?
+    if (typeof customToleranceFn !== 'function') {
+      if (Math.abs(y) <= Math.abs(absoluteTolerance)) return fnParams[independentVariableIdx];
+    } else {
+      if (customToleranceFn(res)) return fnParams[independentVariableIdx];
+    }
+    // set the new guess, correcting for maxStep
+    oldGuess = fnParams[independentVariableIdx];
+    newGuess = oldGuess + y;
+    if (Math.abs(newGuess - oldGuess) > maxStep) {
+      if (newGuess > oldGuess) {
+        newGuess = oldGuess + maxStep;
+      } else {
+        newGuess = oldGuess - maxStep;
+      }
+    }
+    fnParams[independentVariableIdx] = newGuess;
+    // re-run the fn with the new guess
+    y1 = (await fn(...fnParams)) - goal;
+    if (isNaN(y1)) throw IsNanError;
+    // calculate the error
+    g = (y1 - y) / y;
+    if (g === 0) g = 0.0001;
+    // set the new guess based on the error, correcting for maxStep
+    newGuess = oldGuess - y / g;
+    if (maxStep && Math.abs(newGuess - oldGuess) > maxStep) {
+      if (newGuess > oldGuess) {
+        newGuess = oldGuess + maxStep;
+      } else {
+        newGuess = oldGuess - maxStep;
+      }
+    }
+    fnParams[independentVariableIdx] = newGuess;
+  }
+  // done with iterations, and we failed to converge
+  throw FailedToConvergeError;
+}
+
+// const fn = (x, y) => x / y;
+// const fnParams = [2037375, 15897178];
+// const customToleranceFn = (x) => {
+//   return x < 1;
+// };
+
+// try {
+//   const result = goalSeek({
+//     fn,
+//     fnParams,
+//     customToleranceFn,
+//     maxIterations: 1000,
+//     maxStep: 0.01,
+//     goal: 0.15,
+//     independentVariableIdx: 0,
+//   });
+
+//   console.log(`result: ${result}`);
+// } catch (e) {
+//   console.log("error", e);
+// }

+ 220 - 0
src/utils/uploadExcelByUrl.js

@@ -0,0 +1,220 @@
+import LuckyExcel from 'luckyexcel';
+import { getToken } from '@/utils/utils';
+
+const uploadExcelByUrl = (nodeType, versionId, projectName) => {
+  const TEMPLATE_URL = 'https://gt-digitization.oss-cn-hangzhou.aliyuncs.com/public/bom/psr.xlsx';
+
+  return new Promise((resolve, reject) => {
+    LuckyExcel.transformExcelToLuckyByUrl(
+      TEMPLATE_URL,
+      '模板.xlsx',
+      async (exportJson, luckysheetfile) => {
+        let [record] = await getExcel(versionId);
+
+        let len = exportJson.sheets.length;
+        const excelData = exportJson.sheets;
+        delete record.id;
+        record.index = len + '_' + Math.floor(Math.random() * 100);
+        record.status = '0';
+        record.name = '清单';
+        // var sheets = [...excelData, record];
+        var res = [];
+        const category = getCategoryData(record);
+        // 处理Estimate表
+        // initEstimate(sheets[0], category);
+
+        // 处理psr预算
+        excelData[1].status = 1;
+        res.push(initPSR(excelData[1], category, projectName));
+
+        if (nodeType == 4) {
+          // 处理现金流
+          res.push(initActual(excelData[3], category, projectName));
+        }
+
+        res.push(record);
+
+        // 隐藏Estimate表
+        excelData[0].hide = 1;
+        excelData[0].status = 0;
+
+        res.push(excelData[0]);
+
+        resolve(res.map((item, index) => ({ ...item, order: index })));
+      }
+    );
+  });
+};
+
+async function getExcel(gridKey) {
+  var formData = new FormData();
+  formData.append('gridKey', gridKey);
+  let res = await fetch(
+    `/api/v1/purchase/record/sheet?gridKey=${gridKey}&JWT-TOKEN=${getToken()}`,
+    {
+      method: 'POST',
+      body: formData,
+    }
+  ).then(response => response.text());
+  return JSON.parse(JSON.parse(res));
+}
+
+function getCellValue(cell) {
+  let v = '';
+  if (cell.v) {
+    v = cell.v;
+  } else if (cell?.ct?.s) {
+    v = cell.ct.s.map(item => item.v).join('');
+  }
+  return v;
+}
+
+function formatNumber(str) {
+  const number = parseFloat(str);
+  if (!isNaN(number) && number % 1 !== 0) {
+    return number.toFixed(1);
+  }
+  return str;
+}
+// 根据清单获取表分类总价
+function getCategoryData(bom) {
+  let bomData = [],
+    bomTitle = {};
+  bom.celldata.forEach(item => {
+    let v = getCellValue(item.v);
+    if (item.r == 0) {
+      // 设置表头
+      bomTitle[item.c] = v;
+    } else {
+      let key = bomTitle[item.c];
+      if (!bomData[item.r - 1]) {
+        bomData[item.r - 1] = {};
+      }
+      bomData[item.r - 1][key] = v;
+    }
+  });
+  let category = {
+    'GT-UF膜': 0,
+    原平制造: 0,
+    其它膜: 0,
+    水泵: 0,
+    阀门: 0,
+    加药系统: 0,
+    过滤器: 0,
+    空压机: 0,
+    非标: 0,
+    仪表: 0,
+    电气自控: 0,
+    双胞胎硬件: 0,
+    材料: 0,
+    安装: 0,
+    土建: 0,
+    运输: 0,
+    其它: 0,
+  };
+  bomData.forEach(item => {
+    if (category.hasOwnProperty(item['类别'])) {
+      let price = parseFloat(item['总价(元)']);
+      if (isNaN(price)) return;
+      category[item['类别']] += price;
+    }
+  });
+  return category;
+}
+
+// 处理现金流表
+function initActual(actual, category, projectName) {
+  let actualCategory = [];
+  actual.celldata.forEach((item, i, celldata) => {
+    if (item.c == 0 && item.v?.v) {
+      // 处理序号转float出现多余小数的情况
+      item.v.v = formatNumber(item.v?.v);
+    }
+    if (item.c == 1 && item.r == 0) item.v.v = projectName;
+    // 清单分类的总价填入对应预算列
+    if (item.c == 2) {
+      // c=2 为名称列
+      let value = getCellValue(item.v);
+      // 判断该行是否为类型总列
+      if (category.hasOwnProperty(value)) {
+        // 名称后第三列为预算列
+        celldata[i + 3].v.v = category[value];
+      }
+    }
+  });
+  return actual;
+}
+
+// 处理毛利概算表
+function initEstimate(estimate, category) {
+  estimate.celldata.forEach(item => {
+    if (item.r == 15 && item.c == 2) {
+      // 金科制造中心-UF膜
+      item.v.v = category['GT-UF膜'];
+    } else if (item.r == 14 && item.c == 2) {
+      // 金科制造中心设备制造费
+      item.v.v = category['原平制造'];
+    } else if (item.r == 16 && item.c == 2) {
+      // 设备购置费
+      item.v.v =
+        category['其它膜'] +
+        category['水泵'] +
+        category['阀门'] +
+        category['加药系统'] +
+        category['过滤器'] +
+        category['空压机'] +
+        category['非标'] +
+        category['仪表'] +
+        category['电气自控'] +
+        category['材料'] +
+        category['运输'] +
+        category['其它'];
+    } else if (item.r == 18 && item.c == 2) {
+      // 数字双胞胎
+      item.v.v = category['双胞胎硬件'];
+    } else if (item.r == 19 && item.c == 2) {
+      // 安装
+      item.v.v = category['安装'];
+    } else if (item.r == 20 && item.c == 2) {
+      // 土建
+      item.v.v = category['土建'];
+    }
+  });
+  return estimate;
+}
+
+// 处理PSR表预算
+function initPSR(psr, category, projectName) {
+  psr.celldata.forEach(item => {
+    if (item.r == 0 && item.c == 1) {
+      item.v.v = projectName;
+    } else if (item.r == 38 && item.c == 2) {
+      item.v.v = category['GT-UF膜'];
+    } else if (item.r == 37 && item.c == 2) {
+      item.v.v = category['原平制造'];
+    } else if (item.r == 36 && item.c == 2) {
+      item.v.v =
+        category['其它膜'] +
+        category['水泵'] +
+        category['阀门'] +
+        category['加药系统'] +
+        category['过滤器'] +
+        category['空压机'] +
+        category['非标'] +
+        category['仪表'] +
+        category['电气自控'] +
+        category['材料'] +
+        category['运输'] +
+        category['其它'];
+    } else if (item.r == 39 && item.c == 2) {
+      item.v.v = category['双胞胎硬件'];
+    } else if (item.r == 40 && item.c == 2) {
+      item.v.v = category['安装'];
+    } else if (item.r == 41 && item.c == 2) {
+      item.v.v = category['土建'];
+    }
+  });
+  return psr;
+}
+
+export default uploadExcelByUrl;

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio