Parcourir la source

新模板适配

xujunjie il y a 1 an
Parent
commit
a2aab17692

+ 4 - 3
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,
@@ -64,6 +64,7 @@ function CommitAuditModal(props) {
     luckysheet,
     userList,
     templateId,
+    projectList,
     OSSData,
   } = props;
   // console.log(loading);
@@ -159,7 +160,7 @@ function CommitAuditModal(props) {
     if (res.data) {
       const formList = JSON.parse(res.data.json);
       setApprovalProcess(formList.approvalProcess || {});
-      const prevFormData = JSON.parse(formList.formList[0]);
+      const prevFormData = JSON.parse(formList.formList?.[0] || '{}');
       setFormComponentValues(prevFormData);
       return formList;
     }
@@ -537,7 +538,7 @@ function CommitAuditModal(props) {
         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.parse(sheetData);
+        params.data = JSON.stringify(sheetData);
       }
       // params.data = await uploadExcelByUrl(3, version.id);
       console.log(params);

+ 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);

+ 3 - 12
src/pages/Detail/Index.js

@@ -7,18 +7,14 @@ 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';
 import MergeModal from './MergeModal';
-import {GetTokenFromUrl, getToken} from '@/utils/utils';
 import {
   getAuditDetail,
   queryDelSheetRecord,
@@ -28,17 +24,11 @@ import {
 } from '@/services/boom';
 import HistoryDrawer from './HistoryDrawer';
 import AuditFlow from './AuditFlow';
-import {getCurrentUser} from '@/utils/authority';
-import {async} from '@antv/x6/es/registry/marker/async';
-import FileViewerModal from '@/components/FileViewer';
-import PreviewFile from '@/components/PreviewFile';
 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 {
@@ -589,7 +579,8 @@ function Detail(props) {
           onChange={e => exportExcl(e.target.files)}
         />
       </div>
-      <div style={{display: 'flex'}}>
+      <PsrControl sheetRef={sheetRef} />
+      <div style={{ display: 'flex' }}>
         <div
           className={styles.content}
           style={{

+ 50 - 1
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';
@@ -82,6 +83,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 => {
@@ -614,6 +616,53 @@ class LuckySheet extends React.Component {
     return comment;
   }
 
+  async goalSeek(type, goal) {
+    let luckysheet = this.luckysheet;
+
+    const fn = function(x) {
+      return new Promise(resolve => {
+        luckysheet.setCellValue(9, 2, x.toFixed(4), {
+          order: 0,
+          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: 0,
+              });
+              console.log(data);
+              resolve(data);
+            });
+          },
+        });
+      });
+    };
+    try {
+      let defaultValue = luckysheet.getCellValue(9, 2, {
+        order: 0,
+      });
+      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

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

@@ -0,0 +1,77 @@
+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.refreshFormula();
+  };
+  const changeBiddingType = type => {
+    sheetRef.current.luckysheet.setCellValue(102, 1, type, {
+      order: 0,
+    });
+    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;

+ 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);
+// }

+ 35 - 27
src/utils/uploadExcelByUrl.js

@@ -2,42 +2,45 @@ import LuckyExcel from 'luckyexcel';
 import { getToken } from '@/utils/utils';
 
 const uploadExcelByUrl = (nodeType, versionId, projectName) => {
-  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/public/bom/ContractTemplate.xlsx';
+  const TEMPLATE_URL = 'https://gt-digitization.oss-cn-hangzhou.aliyuncs.com/public/bom/psr.xlsx';
 
-  return new Promise((reslove, reject) => {
+  return new Promise((resolve, reject) => {
     LuckyExcel.transformExcelToLuckyByUrl(
-      nodeType == 3 ? TEMPLATE_URL : TEMPLATE_URL2,
+      TEMPLATE_URL,
       '模板.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) };
-        });
+        const excelData = exportJson.sheets;
         delete record.id;
-        record.order = len;
         record.index = len + '_' + Math.floor(Math.random() * 100);
         record.status = '0';
-        record.name = '投标成本';
-        var sheets = [...excelData, record];
+        record.name = '清单';
+        // var sheets = [...excelData, record];
+        var res = [];
         const category = getCategoryData(record);
         // 处理Estimate表
-        initEstimate(sheets[0], category);
+        // initEstimate(sheets[0], category);
 
         // 处理psr预算
-        // initPSR(sheets[1], category);
+        excelData[1].status = 1;
+        res.push(initPSR(excelData[1], category, projectName));
 
         if (nodeType == 4) {
-          // 处理psr预算
-          // initPSR(sheets[2], category);
-          // 处理毛利概算表
-          initActual(sheets[3], category, projectName);
+          // 处理现金流
+          res.push(initActual(excelData[3], category, projectName));
         }
-        reslove(sheets);
+
+        res.push(record);
+
+        // 隐藏Estimate表
+        excelData[0].hide = 1;
+        excelData[0].status = 0;
+
+        res.push(excelData[0]);
+
+        resolve(res.map((item, index) => ({ ...item, order: index })));
       }
     );
   });
@@ -133,12 +136,13 @@ function initActual(actual, category, projectName) {
       // c=2 为名称列
       let value = getCellValue(item.v);
       // 判断该行是否为类型总列
-      if (category.hasOwnProperty('value')) {
+      if (category.hasOwnProperty(value)) {
         // 名称后第三列为预算列
         celldata[i + 3].v.v = category[value];
       }
     }
   });
+  return actual;
 }
 
 // 处理毛利概算表
@@ -176,16 +180,19 @@ function initEstimate(estimate, category) {
       item.v.v = category['土建'];
     }
   });
+  return estimate;
 }
 
 // 处理PSR表预算
-function initPSR(psr, category) {
+function initPSR(psr, category, projectName) {
   psr.celldata.forEach(item => {
-    if (item.r == 39 && item.c == 3) {
+    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 == 38 && item.c == 3) {
+    } else if (item.r == 37 && item.c == 2) {
       item.v.v = category['原平制造'];
-    } else if (item.r == 37 && item.c == 3) {
+    } else if (item.r == 36 && item.c == 2) {
       item.v.v =
         category['其它膜'] +
         category['水泵'] +
@@ -199,14 +206,15 @@ function initPSR(psr, category) {
         category['材料'] +
         category['运输'] +
         category['其它'];
-    } else if (item.r == 40 && item.c == 3) {
+    } else if (item.r == 39 && item.c == 2) {
       item.v.v = category['双胞胎硬件'];
-    } else if (item.r == 41 && item.c == 3) {
+    } else if (item.r == 40 && item.c == 2) {
       item.v.v = category['安装'];
-    } else if (item.r == 42 && item.c == 3) {
+    } else if (item.r == 41 && item.c == 2) {
       item.v.v = category['土建'];
     }
   });
+  return psr;
 }
 
 export default uploadExcelByUrl;