Browse Source

Squashed commit of the following:
权限相关改动
commit 2d0f1949e379de251237a1f04789791fefeda443
Author: Renxy <18510891294@163.com>
Date: Thu Oct 19 15:33:28 2023 +0800

项目列表权限和清单权限

commit cfcca5aac80a86411d1755dbd158a670cc271300
Author: xujunjie <645007605@qq.com>
Date: Fri Oct 13 18:28:16 2023 +0800

update

commit cef3a371044ec6b4a18e278bdb61d45e7dbe7d70
Author: xujunjie <645007605@qq.com>
Date: Fri Oct 13 17:04:02 2023 +0800

luckysheet更新

commit 4a75481f16b6a0bf5790b547b50b9397576b921d
Author: xujunjie <645007605@qq.com>
Date: Fri Oct 13 15:28:54 2023 +0800

公式不刷新的问题

commit 67cd572729299c82ff5f051faa89ff5accc1c758
Author: xujunjie <645007605@qq.com>
Date: Fri Oct 13 13:52:40 2023 +0800

项目选择默认使用当前项目

commit 0a40f8055af99e1596a2328b08f6be5ba6a70a6f
Author: xujunjie <645007605@qq.com>
Date: Fri Oct 13 10:16:29 2023 +0800

修复提交流转报错的问题

commit 24fb5855bf6999ab639c354f6d24839ad1923d3f
Merge: e6af884 0946137
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Thu Oct 12 17:29:36 2023 +0800

Merge branch 'develop' of http://120.55.44.4:10080/xujunjie/BomWeb into develop

commit 09461377fe5b6b7cee24d1b09aebc083226d0657
Author: xujunjie <645007605@qq.com>
Date: Thu Oct 12 15:25:16 2023 +0800

权限更新

commit 92aefb08a6af2517542b9d8985e0fc0a5efd52a6
Author: xujunjie <645007605@qq.com>
Date: Wed Oct 11 18:14:58 2023 +0800

更新lucksheet

commit 76b19ed5a6fe1e9b5ccd93cd3c82922c53dca680
Author: xujunjie <645007605@qq.com>
Date: Wed Oct 11 17:37:46 2023 +0800

更新uckySheet

commit e6af8848354e86ad36fdd464c45e0f5bc92cce94
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Wed Oct 11 16:37:41 2023 +0800

merge conflict issue fix

commit fd0770e531506deb7382448354cf9ed163c41089
Merge: c71c11d ef3a669
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Wed Oct 11 16:34:48 2023 +0800

Merge branch 'develop' of http://120.55.44.4:10080/xujunjie/BomWeb into develop

commit c71c11dde99ce65323f23aabc786189b5823e328
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Wed Oct 11 16:26:51 2023 +0800

fix: 获取不到部分控件默认值问题及默认值不显示问题

commit ef3a66910cf8aedc9d4edc80425ff6be482a094c
Author: xujunjie <645007605@qq.com>
Date: Wed Oct 11 09:30:55 2023 +0800

隐藏现金流表单

commit 499d3eb22e110bf22aff9777375ee60742321f37
Author: xujunjie <645007605@qq.com>
Date: Tue Oct 10 15:56:31 2023 +0800

修复单变量求解

commit ef8ac9116c2ab04b25e1372cb1c6731f1a69350b
Author: xujunjie <645007605@qq.com>
Date: Tue Oct 10 13:49:06 2023 +0800

更新模板

commit 4fb849f736d3e39beaabd500a174ce046cd94c0e
Author: xujunjie <645007605@qq.com>
Date: Mon Oct 9 18:25:58 2023 +0800

流程图type修改

commit 2096f2dc0416b2a5acf58817c18ed6019d9ba528
Author: xujunjie <645007605@qq.com>
Date: Mon Oct 9 18:19:24 2023 +0800

采购合同提交流转节点增加部门模糊搜索功能

commit 559cbafd4fd1ab7a785f1593ab16c7c5b811e723
Author: xujunjie <645007605@qq.com>
Date: Mon Oct 9 17:37:04 2023 +0800

合同总价预测精度调整

commit ed659ffefe820b2fcdd07aa8414dcbe8d3ee2363
Author: xujunjie <645007605@qq.com>
Date: Mon Oct 9 16:49:13 2023 +0800

1. 投标版PSR和签字版PSR节点下的表单只允许查看不允许编辑
2. 修改流程图模板

commit 788f37d44ca970229b20c6927d5da1f0fff016d5
Merge: 8756613 654f2e6
Author: xujunjie <645007605@qq.com>
Date: Mon Oct 9 10:44:45 2023 +0800

Merge branch 'develop' of http://120.55.44.4:10080/xujunjie/BomWeb into develop

commit 875661325ecc65a1846b6353c16f1aaff268c30f
Author: xujunjie <645007605@qq.com>
Date: Mon Oct 9 10:44:42 2023 +0800

模板更新

commit 654f2e62f4a95a92d5b64546a2aa2fadb9ce500a
Merge: e3f8add e5f9304
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Sun Oct 8 18:50:17 2023 +0800

Merge branch 'develop' of http://120.55.44.4:10080/xujunjie/BomWeb into develop

commit e3f8addc785ac2d30ad0aa9583472e046231a7a9
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Sun Oct 8 18:50:00 2023 +0800

fix: 项目控件记录项目id而不是名称和项目编号

commit e5f9304c437a5ab317f4be23552da6b7dc05f2b5
Author: xujunjie <645007605@qq.com>
Date: Sun Oct 8 17:45:22 2023 +0800

更新流程图

commit 4c85baead77db6474bcd31d41ac8aa8471181089
Author: xujunjie <645007605@qq.com>
Date: Sun Oct 8 16:33:15 2023 +0800

1. 更新模板
2. 更新业务流程

commit 5c54a75deaa37b691e47965051bb8d2134ae8575
Author: xujunjie <645007605@qq.com>
Date: Tue Sep 26 10:51:00 2023 +0800

修改详情接口

commit dbcc658bc2de81e6224f1341caea16ce50bbf559
Author: xujunjie <645007605@qq.com>
Date: Thu Sep 21 17:04:08 2023 +0800

节流阀

commit 26816643270588cc773745d03abfcf1788a278e7
Author: xujunjie <645007605@qq.com>
Date: Thu Sep 21 16:53:04 2023 +0800

合同计算

commit 615a29edb4cd14f18672427fae70bbacaeddb0ba
Author: xujunjie <645007605@qq.com>
Date: Thu Sep 21 15:30:24 2023 +0800

更新luckysheet

commit 8e0f6924f2afa19b4fa597967abc5569dcf2a64d
Merge: 1fb2c85 7806b1d
Author: xujunjie <645007605@qq.com>
Date: Thu Sep 21 14:11:17 2023 +0800

Merge branch 'develop' of http://120.55.44.4:10080/xujunjie/BomWeb into develop

commit 1fb2c854305c2e71b7b9921913ef507619e40932
Author: xujunjie <645007605@qq.com>
Date: Thu Sep 21 14:11:13 2023 +0800

设置公式刷新时机

commit 7806b1d5c82aad08e6ea93146b21340ac1942f69
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Thu Sep 21 11:23:58 2023 +0800

chore: 删除debugger用的log

commit 90c3ff4df006f1ad5e5f6abdbb452c8a050d888e
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Thu Sep 21 11:21:11 2023 +0800

fix: label 宽度不足问题

commit ecbe01b2be94c1bebbf862b00201453645b5c5c3
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Thu Sep 21 11:20:24 2023 +0800

fix: 供应商控件变更时返回label

commit 89bac599c8016b7076d9e8e2f481f25d9ff78501
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Thu Sep 21 11:16:34 2023 +0800

fix: 项目控件返回项目名加项目编码

commit 749d7a1815b4b9e347974b71c746cb8c38e0e033
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Thu Sep 21 11:12:35 2023 +0800

fix: 部门控件返回 部门名而不是部门id

commit 82bcddeb3209b01a281d2e714f5cc0be02252701
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Thu Sep 21 10:20:58 2023 +0800

fix: DIYTable保存时多次stringify问题

commit 23eb691526cef9e7f35ded35292d7575e26b3b63
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Wed Sep 20 20:42:25 2023 +0800

fix: DIYTable 提交时的格式问题

commit b5c9266ed3e630b82078de149f5d82807a239124
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Wed Sep 20 19:20:22 2023 +0800

fix: 去除开发debugger代码

commit d76b078d1aa06e65c315cd0cab6b6ed2ae9c630c
Merge: 9cd7595 c510763
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Wed Sep 20 18:59:29 2023 +0800

Merge branch 'develop' of http://120.55.44.4:10080/xujunjie/BomWeb into develop

commit 9cd759517d6833d0e6bec93ed93b871c98b8774d
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Wed Sep 20 18:59:12 2023 +0800

feat: DIYTable控件 提交时value stringify 为字符串

commit fe32a7e64c1d5d2fc61e549373266c310f862534
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Wed Sep 20 18:58:37 2023 +0800

fix: 部门控件返回字符串

commit c510763d3107ee0c419a007910aeb1f771efff9e
Author: xujunjie <645007605@qq.com>
Date: Tue Sep 19 14:53:46 2023 +0800

fix

commit e9a2f99a12927d7a8ffa6ec4d60bdba3f724afda
Author: xujunjie <645007605@qq.com>
Date: Tue Sep 19 10:58:36 2023 +0800

修复提交流转失败的问题

commit f69b4f2dd3f02872bf2bf36ed54a2b02403351e7
Merge: 99f9c9f b96ce61
Author: xujunjie <645007605@qq.com>
Date: Tue Sep 19 10:49:23 2023 +0800

Merge branch 'develop' of http://120.55.44.4:10080/xujunjie/BomWeb into develop

commit 99f9c9f4530f637093247c0edabdc2f5d8fe85ce
Author: xujunjie <645007605@qq.com>
Date: Tue Sep 19 10:49:12 2023 +0800

fix

commit b96ce610fe6d8cfda1e8432f6e6f98e79d31c8c5
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Thu Sep 14 15:15:17 2023 +0800

fix: 供应商控件不显示问题修复

commit ba7d25227fd3bb24cff55f91356f5191d9c2da1b
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Thu Sep 14 13:47:10 2023 +0800

fix: DIYTable 渲染问题修复

commit 30793f09d81e01878adb0342d3d5ab769d0c862b
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Wed Sep 13 17:26:42 2023 +0800

fix: 限制DIYTable可选择的控件

commit 8b86c94e58b54d40e3f012f0cc61a2d36102d2f1
Merge: 49e63ae 12d3f04
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Wed Sep 13 16:25:24 2023 +0800

Merge remote-tracking branch 'origin/develop' into unsure

commit 49e63aea0ab3cc655638c124c8ab9735777b77a0
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Wed Sep 13 11:05:14 2023 +0800

fix: 保存时DIYTable值被覆写问题

commit 44fbf45e3067e04dfcf6067da510fe6bcecee279
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Wed Sep 13 11:04:40 2023 +0800

fix: DIYTable控件仅显示时不渲染问题修复

commit 12d3f0452a80ff23373968d2cacf87cc24ae4d38
Author: xujunjie <645007605@qq.com>
Date: Wed Sep 13 10:26:04 2023 +0800

更新luckysheet

commit 01a6291850639a0bdf66c403f9f6c5778f653582
Author: xujunjie <645007605@qq.com>
Date: Wed Sep 13 09:32:59 2023 +0800

fix

commit ec36ba7c6ca5823581b309399ed6e71234459176
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Tue Sep 12 20:10:02 2023 +0800

fix: 去除上次提交时保存的值对本次提交的影响,记录所有form的值而不是只变更的值

commit 3baf365367fff45235db76ac1b8965576f273295
Author: xujunjie <645007605@qq.com>
Date: Tue Sep 12 16:41:39 2023 +0800

权限修改

commit aa8ec139766917bb856741f19a06ff7a9c2ff2a2
Author: xujunjie <645007605@qq.com>
Date: Tue Sep 12 15:27:58 2023 +0800

luckysheet更新

commit 5090d50a607f4ab9b3521974041a217ecb48cc31
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Tue Sep 12 14:30:45 2023 +0800

merge conflict resolve

commit 859af75038252734fae42425d9aee66510d730ef
Merge: 86d2c9a 61d547f
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Tue Sep 12 14:28:19 2023 +0800

Merge remote-tracking branch 'origin/develop' into unsure

commit 86d2c9ab64af3e1e2fdf6b4aae11b52d7c010a9c
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Tue Sep 12 14:26:09 2023 +0800

feat: 添加Text Note 控件

commit ac3f4deb763b486950ddc048a943c3753cdced9f
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Tue Sep 12 10:52:14 2023 +0800

synchronization

commit 07d30f360296b28051449ffcfe35572f1c3dcd1c
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Tue Sep 12 10:34:26 2023 +0800

resolve conflict form remote

commit 6554f56abca0507b13f8b87f252505f7806f98e0
Merge: 991636e 61d547f
Author: xujunjie <645007605@qq.com>
Date: Tue Sep 12 09:21:23 2023 +0800

Merge branch 'develop'

commit 61d547f5d0680f0db6f73a1978918352e1f6c145
Author: xujunjie <645007605@qq.com>
Date: Tue Sep 12 09:20:58 2023 +0800

回退功能

commit f3f4f6715505a10dc1583190ec4f7ec8c8c3c97d
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Mon Sep 11 19:05:09 2023 +0800

synchronization

commit 13c2159282f7cb9fe20b5318e0b1f246b9c67e19
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Mon Sep 11 14:59:40 2023 +0800

synchronization

commit e3d410abd570a1817d59410c4903d036fdb9bcd0
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Fri Sep 8 18:43:54 2023 +0800

synchronization

commit d4167ee391e0f0ae54ee8e6dc2b05805c4c7c435
Merge: f822246 e9f85b0
Author: xujunjie <645007605@qq.com>
Date: Fri Sep 8 10:14:49 2023 +0800

Merge branch 'develop' of http://120.55.44.4:10080/xujunjie/BomWeb into develop

commit f822246b56b35e2e8af24a0ab1e7849f4b5def42
Author: xujunjie <645007605@qq.com>
Date: Fri Sep 8 10:14:47 2023 +0800

增加回退功能

commit e9f85b0152dc5bdbb6e6b29231a9fb2610d9e944
Author: Renxy <18510891294@163.com>
Date: Thu Sep 7 18:12:24 2023 +0800

添加权限

commit 218cc5180fbe38aa885ac2c9beab5d95dc865a92
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Thu Sep 7 14:35:48 2023 +0800

fix: 提交流转页面DIYTable填写功能

commit 8ba2b96960bda6bf4fdea88045a451084545497d
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Thu Sep 7 14:34:18 2023 +0800

fix: DIYTable onChange 事件不刷新问题

commit a524949fe0cef2287675b3466e9efdc6c1b4b2d0
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Wed Sep 6 18:18:46 2023 +0800

synchronization

commit 3958fce399904001dee832a0d14038b01764c6f8
Merge: f2c6051 fe85620
Author: xujunjie <645007605@qq.com>
Date: Tue Sep 5 14:42:31 2023 +0800

Merge branch 'temp' into develop

commit fe8562052e7db53953c0eeefbfa3cf5937ab3afb
Merge: 1489cc8 9020caf
Author: xujunjie <645007605@qq.com>
Date: Tue Sep 5 14:42:12 2023 +0800

Merge branch 'develop' into temp

commit 77bbb353c96a8370edf7f5933607aff8f7d43237
Merge: 9eb3994 f2c6051
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Tue Sep 5 14:19:30 2023 +0800

Merge branch 'develop' of http://120.55.44.4:10080/xujunjie/BomWeb into unsure

commit 991636e20ca4bfc5456c0878ed382406248e96ad
Merge: e0f3ed2 cc7b287
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Thu Aug 24 14:33:09 2023 +0800

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

commit e0f3ed27c622bb9112e0692573ddaa8f65de702b
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Thu Aug 24 14:33:04 2023 +0800

config: modify flow detail config file

commit 1489cc8cf42403d7cdd61ac92cb07c9d34ac98cf
Author: xujunjie <645007605@qq.com>
Date: Wed Aug 9 14:44:27 2023 +0800

temp

commit 9eb3994f2f406d3e9b177132ead8b32fee38b026
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Mon Aug 7 09:46:10 2023 +0800

feat: 增加公式计算插件配置

commit b7e862ff25379a3ce1c1e6c5d7f6c3e0eace6ce1
Merge: 7054cc8 9020caf
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Sat Aug 5 16:07:13 2023 +0800

Merge branch 'develop' into unsure

commit a2aab176922f5495eb82f5d094c9d1ae446bb119
Author: xujunjie <645007605@qq.com>
Date: Sat Aug 5 13:51:22 2023 +0800

新模板适配

commit 7054cc8da789cf550a574c2ac47fd82406aa83c5
Merge: 27c4b3a cd4908a
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Fri Aug 4 19:15:58 2023 +0800

Merge branch 'develop' into unsure

commit 27c4b3a844b4000c04267584df745cecf3832072
Author: ZhaoJun <jun.zhao@greentech.com.cn>
Date: Thu Aug 3 16:11:43 2023 +0800

added DIY table form

commit 922867fd839b33705b8ecde2e4fa1f5e4b208854
Merge: e2521c1 8a3aa32
Author: xujunjie <645007605@qq.com>
Date: Wed Aug 2 09:45:44 2023 +0800

Merge branch 'master' into temp

commit e2521c1ec8a0ab0c1f096bdff295f7ec33428086
Author: xjj <645007605@qq.com>
Date: Mon Jul 24 13:45:11 2023 +0800

psr改动

Renxy 1 year ago
parent
commit
878447d4ae
46 changed files with 2847 additions and 781 deletions
  1. 1 1
      config/config.js
  2. 0 0
      public/Luckysheet/luckysheet.umd.js
  3. 0 0
      public/Luckysheet/luckysheet.umd.js.map
  4. 38 24
      src/components/AuditForm/ComponentLibrary.js
  5. 125 15
      src/components/AuditForm/FormContent.js
  6. 145 0
      src/components/AuditForm/FormulaModal.js
  7. 174 5
      src/components/AuditForm/ItemAttribute.js
  8. 63 0
      src/components/AuditForm/constant.js
  9. 140 20
      src/components/AuditForm/index.js
  10. 49 0
      src/components/DDComponents/CodeFiled/index.js
  11. 1 0
      src/components/DDComponents/DDAttachment/index.js
  12. 8 2
      src/components/DDComponents/DDDateField/index.js
  13. 1 1
      src/components/DDComponents/DDSelectField/index.js
  14. 7 0
      src/components/DDComponents/DIYTable/index.css
  15. 231 0
      src/components/DDComponents/DIYTable/index.tsx
  16. 77 26
      src/components/DDComponents/DepartmentField/index.js
  17. 14 0
      src/components/DDComponents/FormulaField/index.js
  18. 55 0
      src/components/DDComponents/ManufacturerField/index.js
  19. 1 1
      src/components/DDComponents/NumberField/index.js
  20. 51 0
      src/components/DDComponents/ProjectField/index.js
  21. 9 0
      src/components/DDComponents/TextNote/index.js
  22. 88 42
      src/components/DDComponents/index.js
  23. 1 0
      src/components/Flow/node/rect/mapServe.tsx
  24. 17 1
      src/models/user.js
  25. 81 89
      src/pages/Detail/AuditDetailed.js
  26. 67 8
      src/pages/Detail/AuditModal.js
  27. 537 331
      src/pages/Detail/CommitAuditModal.js
  28. 1 2
      src/pages/Detail/CurrentInfo.js
  29. 1 1
      src/pages/Detail/DropdownMenu.js
  30. 24 1
      src/pages/Detail/FlowModal.js
  31. 55 11
      src/pages/Detail/FormAndFilesNode.js
  32. 30 13
      src/pages/Detail/Index.js
  33. 146 4
      src/pages/Detail/LuckySheet.js
  34. 89 0
      src/pages/Detail/PsrControl.js
  35. 30 22
      src/pages/Detail/TimeNode.js
  36. 10 6
      src/pages/Detail/models/detail.js
  37. 13 81
      src/pages/Flow/FlowDetail.json
  38. 3 3
      src/pages/Flow/models/flow.js
  39. 4 4
      src/pages/List/models/list.js
  40. 27 65
      src/pages/Temp/index.js
  41. 8 1
      src/services/approval.js
  42. 6 0
      src/services/boom.js
  43. 24 0
      src/services/contract.js
  44. 121 0
      src/utils/GoalSeek.js
  45. 5 1
      src/utils/request.js
  46. 269 0
      src/utils/uploadExcelByUrl.js

+ 1 - 1
config/config.js

@@ -143,7 +143,7 @@ export default {
       // target: 'http://192.168.20.53:8888/',
       // target: 'http://120.55.44.4:8896/',
       // target: 'http://47.96.12.136:8888/',
-      target: 'http://47.96.12.136:8896/',
+      target: 'http://47.96.12.136:8895/',
       // target: 'http://oraysmart.com:8889/',
       // target: 'http://oraysmart.com:8888/api',
       // changeOrigin: true,

File diff suppressed because it is too large
+ 0 - 0
public/Luckysheet/luckysheet.umd.js


File diff suppressed because it is too large
+ 0 - 0
public/Luckysheet/luckysheet.umd.js.map


+ 38 - 24
src/components/AuditForm/ComponentLibrary.js

@@ -3,16 +3,28 @@ import React, { useState } from 'react';
 import { COMPONENT_LIST } from './constant';
 
 function ComponentLibrary(props) {
-  const { visible, onCancel, onOk } = props;
-  const [currnetItem, setCurrentItem] = useState(null);
+  const { visible, onCancel, onOk, addToTable } = props;
+  const [currentItem, setCurrentItem] = useState(null);
+
+  const DIYTableComponents = [
+    'DDSelectField',
+    'DDDateField',
+    'NumberField',
+    'TextField',
+    'TextNote',
+  ];
+
+  const componenetList = COMPONENT_LIST.filter(item =>
+    addToTable ? DIYTableComponents.includes(item.componentName) : true
+  );
 
   const handleOk = () => {
-    if (!currnetItem) {
+    if (!currentItem) {
       message.error('请选择控件');
       return;
     }
     setCurrentItem(null);
-    onOk?.(currnetItem);
+    onOk?.(currentItem);
   };
   const handleCancel = () => {
     setCurrentItem(null);
@@ -21,26 +33,28 @@ function ComponentLibrary(props) {
   return (
     <Modal visible={visible} onCancel={handleCancel} onOk={handleOk}>
       <Row gutter={12} style={{ paddingTop: 20 }}>
-        {COMPONENT_LIST.map(item => (
-          <Col span={8}>
-            <div
-              onClick={() => setCurrentItem(item)}
-              style={{
-                display: 'flex',
-                justifyContent: 'flex-start',
-                alignItems: 'center',
-                border: item == currnetItem ? '1px solid #1890FF' : '1px solid #aaa',
-                width: '100%',
-                padding: '4px 12px',
-                cursor: 'pointer',
-                margin: '10px 0',
-              }}
-            >
-              {item.icon}
-              <span style={{ marginLeft: 8 }}>{item.props.label}</span>
-            </div>
-          </Col>
-        ))}
+        {componenetList.map(item => {
+          return (
+            <Col span={8}>
+              <div
+                onClick={() => setCurrentItem(item)}
+                style={{
+                  display: 'flex',
+                  justifyContent: 'flex-start',
+                  alignItems: 'center',
+                  border: item === currentItem ? '1px solid #1890FF' : '1px solid #aaa',
+                  width: '100%',
+                  padding: '4px 12px',
+                  cursor: 'pointer',
+                  margin: '10px 0',
+                }}
+              >
+                {item.icon}
+                <span style={{ marginLeft: 8 }}>{item.props.label}</span>
+              </div>
+            </Col>
+          );
+        })}
       </Row>
     </Modal>
   );

+ 125 - 15
src/components/AuditForm/FormContent.js

@@ -1,9 +1,14 @@
-import { Form } from 'antd';
+import { Button, Form } from 'antd';
 import React, { useState } from 'react';
-import { ArrowUpOutlined, ArrowDownOutlined, DeleteOutlined } from '@ant-design/icons';
+import {
+  ArrowUpOutlined,
+  ArrowDownOutlined,
+  DeleteOutlined,
+  PlusOutlined,
+} from '@ant-design/icons';
 
 function FormContent(props) {
-  const { list, onChange, onSelect } = props;
+  const { list, onChange, onSelect, onTableColumnChange } = props;
   const [currentItem, setCurrentItem] = useState(null);
   const handleDelete = index => {
     let _list = [...list];
@@ -25,9 +30,9 @@ function FormContent(props) {
     _list[index] = temp;
     onChange(_list);
   };
-  const handleSelect = index => {
-    setCurrentItem(index);
-    onSelect(index);
+  const handleFormItemClick = id => {
+    setCurrentItem(id[0]);
+    onSelect(id);
   };
   return (
     <div style={{ width: 300 }}>
@@ -54,9 +59,10 @@ function FormContent(props) {
         );
         return (
           <FormItem
-            key={item.id}
-            active={index == currentItem}
-            onClick={() => handleSelect(index)}
+            key={item.props?.id}
+            active={item.props?.id === currentItem}
+            onClick={handleFormItemClick}
+            onTableColumnChange={onTableColumnChange}
             item={item}
             btns={btns}
           />
@@ -67,24 +73,128 @@ function FormContent(props) {
 }
 
 function FormItem(props) {
-  const { item, btns, active, onClick } = props;
+  const { item, btns, active, onClick, onTableColumnChange } = props;
   const { label, placeholder, required } = item.props;
+
+  // 子控件激活id
+  const [selectColumnID, setSelectColumnID] = useState('');
+
+  // 新增列时通过id定位
+  const addTableColumn = event => {
+    // 记录当前表格uuid
+    onTableColumnChange(item.props.id);
+  };
+
+  // 修改表格内部的控件顺序
+  const changeIndex = (index, operate) => {
+    const newCol = [...item.columns];
+    const prev = newCol[index - 1];
+    const next = newCol[index + 1];
+    switch (operate) {
+      case 'up':
+        newCol[index - 1] = newCol[index];
+        newCol[index] = prev;
+        break;
+      case 'down':
+        newCol[index + 1] = newCol[index];
+        newCol[index] = next;
+        break;
+      case 'delete':
+        newCol.splice(index, 1);
+        break;
+      default:
+        break;
+    }
+    onTableColumnChange(item.props.id, newCol);
+  };
   return (
     <div
       style={{
         marginBottom: 20,
         padding: '4px 12px',
         border: '1px solid #666',
-        borderLeft: active ? '10px solid #1890FF' : '1px solid #666',
+        borderLeft: active && !item.isTable ? '10px solid #1890FF' : '1px solid #666',
+      }}
+      onClick={e => {
+        e.stopPropagation();
+        if (item.isColumn === undefined) {
+          onClick([item.props.id]);
+        } else {
+          onClick();
+        }
       }}
-      onClick={onClick}
     >
-      <div style={{ fontSzie: 24, color: '#000', fontWeight: 'bold', position: 'relative' }}>
+      <div
+        style={{
+          fontSize: 16,
+          color: '#000',
+          fontWeight: 'bold',
+          position: 'relative',
+        }}
+      >
         {required && <i style={{ color: 'red' }}>*</i>}
         {label}
-        <div style={{ position: 'absolute', right: 0, top: 0, padding: '5px 10px' }}>{btns}</div>
+        <div
+          style={{
+            position: 'absolute',
+            right: 0,
+            top: 0,
+            padding: '5px 10px',
+          }}
+        >
+          {btns}
+        </div>
       </div>
-      <div style={{ color: '#999', fontSize: 16 }}>{placeholder}</div>
+      {item.isTable ? (
+        <div style={{ padding: '10px 0 5px 0' }}>
+          {item.columns.map((column, index) => {
+            // column的按钮和外部的控件按钮不一样
+            const colBtns = (
+              <>
+                {index !== 0 && (
+                  <ArrowUpOutlined
+                    style={{ marginLeft: 5, cursor: 'pointer' }}
+                    onClick={() => {
+                      changeIndex(index, 'up');
+                    }}
+                  />
+                )}
+                {index !== item.columns.length - 1 && (
+                  <ArrowDownOutlined
+                    style={{ marginLeft: 5, cursor: 'pointer' }}
+                    onClick={() => {
+                      changeIndex(index, 'down');
+                    }}
+                  />
+                )}
+                <DeleteOutlined
+                  style={{ marginLeft: 5, cursor: 'pointer' }}
+                  onClick={() => {
+                    changeIndex(index, 'delete');
+                  }}
+                />
+              </>
+            );
+            return (
+              <FormItem
+                key={column.props?.id}
+                item={column}
+                active={active && column.props?.id === selectColumnID}
+                onClick={() => {
+                  setSelectColumnID(column.props.id);
+                  onClick([item.props.id, column.props.id]);
+                }}
+                btns={colBtns}
+              />
+            );
+          })}
+          <Button type="dashed" block onClick={addTableColumn} icon={<PlusOutlined />}>
+            点击添加列
+          </Button>
+        </div>
+      ) : (
+        <div style={{ color: '#999', fontSize: 16 }}>{placeholder}</div>
+      )}
     </div>
   );
 }

+ 145 - 0
src/components/AuditForm/FormulaModal.js

@@ -0,0 +1,145 @@
+import React, { useEffect, useMemo, useState } from 'react';
+import { Divider, Input, Modal, message, Button } from 'antd';
+
+const { TextArea } = Input;
+
+export const FormulaType = {
+  Filed: 'filed',
+  Symbol: 'symbol',
+  Number: 'number',
+};
+
+const FormulaModal = props => {
+  const { item, numFiledList = [], visible, onCancel, onChange } = props;
+  const symbolList = ['+', '-', '*', '/', '(', ')'];
+  const numberList = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '.'];
+
+  const [formula, setFormula] = useState([]);
+
+  useEffect(() => {
+    if (!visible) setFormula([]);
+    if (item?.props) setFormula(item.props.formula || []);
+  }, [visible, item]);
+
+  const value = useMemo(() => {
+    const strList = formula.map(fItem =>
+      fItem.type === FormulaType.Filed ? `【${fItem.label}】` : fItem.label
+    );
+    return `计算公式=${strList.join('')}`;
+  }, [formula]);
+  console.log('-----------------', formula);
+
+  const isSameBeforeItem = type => {
+    return formula.length > 0 && formula[formula.length - 1].type == type;
+  };
+
+  const handlerChange = (type, item) => {
+    let obj = {};
+    console.log(formula[formula.length - 1]);
+    switch (type) {
+      case FormulaType.Filed:
+        if (isSameBeforeItem(FormulaType.Filed)) {
+          message.error('不能选择连续两个组件');
+        }
+        obj = {
+          type: FormulaType.Filed,
+          id: item.props.id,
+          label: item.props.label,
+        };
+        setFormula([...formula, obj]);
+        break;
+      case FormulaType.Symbol:
+        if (isSameBeforeItem(FormulaType.Symbol)) {
+          message.error('不能选择连续两个符号');
+        }
+        obj = { type: FormulaType.Symbol, label: item };
+        setFormula([...formula, obj]);
+        break;
+      case FormulaType.Number:
+        // 如果前一个选择的也是数字则合并数字 否则直接添加
+        if (isSameBeforeItem(FormulaType.Number)) {
+          const len = formula.length - 1;
+          const label = formula[len].label + item;
+          obj = { type: FormulaType.Number, label };
+          const result = [...formula];
+          result[len] = obj;
+          setFormula(result);
+        } else {
+          obj = { type: FormulaType.Number, label: item };
+          setFormula([...formula, obj]);
+        }
+        break;
+      default:
+        break;
+    }
+  };
+
+  return (
+    <Modal
+      title="编辑计算公式"
+      open={visible}
+      onCancel={onCancel}
+      onOk={() => {
+        onChange?.({ ...item.props, formula, formulaLabel: value });
+      }}
+      width={1000}
+      pagination={false}
+    >
+      <Divider />
+
+      <Button type="primary" style={{ marginBottom: '20px' }} onClick={() => setFormula([])}>
+        清空
+      </Button>
+      <TextArea value={value} />
+      <div style={{ margin: '20px 20px 20px 0' }}>
+        计算对象:
+        {numFiledList.map((nItem, idx) => (
+          <span
+            style={{
+              padding: '6px 10px',
+              border: '1px solid #e5e5e5',
+              borderRadius: '2px',
+              marginRight: '10px',
+            }}
+            onClick={() => handlerChange(FormulaType.Filed, nItem)}
+          >
+            {nItem.props.label}
+          </span>
+        ))}
+      </div>
+      <div style={{ margin: '0 20px 20px 0' }}>
+        计算符号:
+        {symbolList.map((sItem, idx) => (
+          <span
+            style={{
+              padding: '6px 10px',
+              border: '1px solid #e5e5e5',
+              borderRadius: '2px',
+              marginRight: '10px',
+            }}
+            onClick={() => handlerChange(FormulaType.Symbol, sItem)}
+          >
+            {sItem}
+          </span>
+        ))}
+      </div>
+      <div style={{ margin: '0 20px 20px 0' }}>
+        数字键盘:
+        {numberList.map((nItem, idx) => (
+          <span
+            style={{
+              padding: '6px 10px',
+              border: '1px solid #e5e5e5',
+              borderRadius: '2px',
+              marginRight: '10px',
+            }}
+            onClick={() => handlerChange(FormulaType.Number, nItem)}
+          >
+            {nItem}
+          </span>
+        ))}
+      </div>
+    </Modal>
+  );
+};
+export default FormulaModal;

+ 174 - 5
src/components/AuditForm/ItemAttribute.js

@@ -2,8 +2,8 @@ import { Form, Button, Switch, Input, Radio, Space, Row } from 'antd';
 import React, { useMemo, useState, useEffect } from 'react';
 import { DeleteOutlined } from '@ant-design/icons';
 function ItemAttribute(props) {
-  const { item, onChange } = props;
-
+  const { item, onChange, onRelClick, onFormulaClick } = props;
+  if (!item) return null;
   const renderForm = () => {
     let FormContent;
     const formProps = {
@@ -42,16 +42,34 @@ function ItemAttribute(props) {
       case 'NumberField':
         FormContent = <NumberField {...formProps} />;
         break;
+      case 'TextNote':
+        FormContent = <TextNote {...formProps} />;
+        break;
       case 'DDDateField':
         FormContent = <DDDateField {...formProps} />;
         break;
+      case 'DIYTable':
+        FormContent = <DIYTableField {...formProps} />;
+        break;
+      case 'FormulaField':
+        FormContent = <FormulaField {...formProps} onFormulaClick={onFormulaClick} />;
+        break;
+      case 'ProjectField':
+        FormContent = <ProjectField {...formProps} onFormulaClick={onFormulaClick} />;
+        break;
+      case 'ManufacturerField':
+        FormContent = <ManufacturerField {...formProps} />;
+        break;
+      case 'CodeField':
+        FormContent = <CodeField {...formProps} />;
+        break;
+      default:
+        FormContent = null;
+        break;
     }
-
     return FormContent;
   };
 
-  if (!item) return null;
-
   return (
     <div
       style={{
@@ -412,3 +430,154 @@ function NumberField(props) {
     </Form>
   );
 }
+
+function TextNote(props) {
+  const { item, btns, onFinish } = props;
+  const [form] = Form.useForm();
+
+  return (
+    <Form
+      form={form}
+      labelCol={{ span: 6 }}
+      wrapperCol={{ span: 18 }}
+      onFinish={onFinish}
+      autoComplete="off"
+      initialValues={item.props}
+    >
+      <Form.Item label="说明文字" name="placeholder">
+        <Input />
+      </Form.Item>
+      {btns}
+    </Form>
+  );
+}
+
+function ProjectField(props) {
+  const { item, btns, onFinish } = props;
+  const [form] = Form.useForm();
+  return (
+    <Form
+      form={form}
+      labelCol={{ span: 6 }}
+      wrapperCol={{ span: 20 }}
+      autoComplete="off"
+      initialValues={item.props}
+      onFinish={onFinish}
+    >
+      <Form.Item label="标题" name="label">
+        <Input />
+      </Form.Item>
+      <Form.Item label="提示文字" name="placeholder">
+        <Input />
+      </Form.Item>
+      <Form.Item label="必填" name="required" valuePropName="checked">
+        <Switch />
+      </Form.Item>
+      {btns}
+    </Form>
+  );
+}
+
+function DIYTableField(props) {
+  const { item, btns, onFinish } = props;
+  const [form] = Form.useForm();
+  return (
+    <Form
+      form={form}
+      labelCol={{ span: 6 }}
+      wrapperCol={{ span: 18 }}
+      autoComplete="off"
+      initialValues={item.props}
+      onFinish={onFinish}
+    >
+      <Form.Item label="表格名称" name="label">
+        <Input />
+      </Form.Item>
+      {btns}
+    </Form>
+  );
+}
+
+function FormulaField(props) {
+  const { item, btns, onFinish, onFormulaClick } = props;
+  const [form] = Form.useForm();
+  const value = useMemo(() => {
+    return item.props.formulaLabel;
+  }, [item.props]);
+  return (
+    <Form
+      form={form}
+      labelCol={{ span: 6 }}
+      wrapperCol={{ span: 18 }}
+      autoComplete="off"
+      initialValues={item.props}
+      onFinish={values => onFinish({ ...item.props, ...values })}
+    >
+      <Form.Item label="标题" name="label">
+        <Input />
+      </Form.Item>
+      <Form.Item label="提示文字" name="placeholder">
+        <Input />
+      </Form.Item>
+      <Form.Item label="计算公式">
+        <Input value={value} onClick={onFormulaClick} />
+      </Form.Item>
+      <Form.Item label="必填" name="required" valuePropName="checked">
+        <Switch />
+      </Form.Item>
+      {btns}
+    </Form>
+  );
+}
+
+function ManufacturerField(props) {
+  const { item, btns, onFinish } = props;
+  const [form] = Form.useForm();
+  return (
+    <Form
+      form={form}
+      labelCol={{ span: 6 }}
+      wrapperCol={{ span: 18 }}
+      autoComplete="off"
+      initialValues={item.props}
+      onFinish={onFinish}
+    >
+      <Form.Item label="标题" name="label">
+        <Input />
+      </Form.Item>
+      <Form.Item label="提示文字" name="placeholder">
+        <Input />
+      </Form.Item>
+      <Form.Item label="必填" name="required" valuePropName="checked">
+        <Switch />
+      </Form.Item>
+      {btns}
+    </Form>
+  );
+}
+
+function CodeField(props) {
+  const { item, btns, onFinish } = props;
+  const [form] = Form.useForm();
+  return (
+    <Form
+      form={form}
+      labelCol={{ span: 6 }}
+      wrapperCol={{ span: 18 }}
+      autoComplete="off"
+      initialValues={item.props}
+      onFinish={onFinish}
+    >
+      <Form.Item label="标题" name="label">
+        <Input />
+      </Form.Item>
+      <Form.Item label="提示文字" name="placeholder">
+        <Input />
+      </Form.Item>
+      <Form.Item label="必填" name="required" valuePropName="checked">
+        <Switch />
+      </Form.Item>
+      {btns}
+    </Form>
+  );
+}

+ 63 - 0
src/components/AuditForm/constant.js

@@ -1,3 +1,4 @@
+import React from 'react';
 import {
   UserOutlined,
   TeamOutlined,
@@ -7,6 +8,12 @@ import {
   BlockOutlined,
   FieldNumberOutlined,
   InsertRowAboveOutlined,
+  TableOutlined,
+  LaptopOutlined,
+  ProjectOutlined,
+  SolutionOutlined,
+  NumberOutlined,
+  FontColorsOutlined,
 } from '@ant-design/icons';
 
 export const COMPONENT_LIST = [
@@ -30,6 +37,36 @@ export const COMPONENT_LIST = [
       choice: '0',
     },
   },
+  {
+    componentName: 'ProjectField',
+    icon: <ProjectOutlined />,
+    props: {
+      label: '选择项目',
+      placeholder: '请选择项目',
+      required: false,
+      choice: '0',
+    },
+  },
+  {
+    componentName: 'ManufacturerField',
+    icon: <SolutionOutlined />,
+    props: {
+      label: '选择供应商',
+      placeholder: '请选择供应商',
+      required: false,
+      // choice: '0',
+    },
+  },
+  {
+    componentName: 'TextNote',
+    icon: <FontColorsOutlined />,
+    props: {
+      label: '文本说明控件',
+      bizAlias: '',
+      placeholder: '请输入文本',
+      required: false,
+    },
+  },
   {
     componentName: 'TextField',
     icon: <ItalicOutlined />,
@@ -88,4 +125,30 @@ export const COMPONENT_LIST = [
       unit: '',
     },
   },
+  {
+    componentName: 'CodeField',
+    icon: <NumberOutlined />,
+    props: {
+      label: '合同编号',
+      placeholder: '自动生成',
+      required: false,
+    },
+  },
+  {
+    componentName: 'DIYTable',
+    icon: <TableOutlined />,
+    props: {
+      label: 'DIY表格',
+      required: false,
+    },
+  },
+  {
+    componentName: 'FormulaField',
+    icon: <LaptopOutlined />,
+    props: {
+      label: '计算公式',
+      placeholder: '自动计算数值',
+      required: false,
+    },
+  },
 ];

+ 140 - 20
src/components/AuditForm/index.js

@@ -1,42 +1,135 @@
 import React, { useState, useEffect } from 'react';
+import { uuidv4 } from '@antv/xflow';
+import { Button } from 'antd';
 import FormContent from './FormContent';
 import ComponentLibrary from './ComponentLibrary';
 import ItemAttribute from './ItemAttribute';
-import { uuidv4 } from '@antv/xflow';
-import { Button } from 'antd';
+import FormulaModal from './FormulaModal';
 
 function AuditForm(props) {
   const { value, onChange } = props;
   const [formList, setFormList] = useState([]);
   const [select, setSelect] = useState(-1);
+  const [selectList, setSelectList] = useState([]);
   const [visible, setVisible] = useState(false);
-
-  const handleAddItem = item => {
-    const formItem = generateItem(item);
-    handleChangeList([...formList, formItem]);
-    setVisible(false);
-  };
+  const [forVisible, setForVisible] = useState(false); // 计算公式弹窗
+  const [addToTable, setAddToTable] = useState(false);
+  const [currentTableID, setCurrentTableID] = useState('');
 
   const generateItem = item => {
-    let newItem = {
+    const newItem = {
       ...item,
       props: { ...item.props, id: `${item.componentName}_${uuidv4()}` },
     };
+    // 如果是表格的话
+    if (item.componentName === 'DIYTable') {
+      newItem.columns = [];
+      newItem.isTable = true;
+    }
     delete newItem.icon;
     return newItem;
   };
 
-  const onChangeAttribute = newItem => {
-    let oldValue = formList[select].props;
-    formList[select].props = { ...oldValue, ...newItem };
-    handleChangeList([...formList]);
-  };
-
   const handleChangeList = list => {
     setFormList(list);
     onChange?.(list);
   };
 
+  const handleAddItem = item => {
+    if (addToTable) {
+      const column = generateItem(item);
+      // 找到对应的表格
+      const tableItem = formList.find(tItem => tItem.props.id === currentTableID);
+      column.isColumn = true;
+      // 把新增的列加入到表格中
+      tableItem.columns.push(column);
+      // 把新增列的表格放回
+      const newFormList = [];
+      for (const item of formList) {
+        if (item.props.id !== currentTableID) {
+          newFormList.push(item);
+        } else {
+          newFormList.push(tableItem);
+        }
+      }
+      handleChangeList(newFormList);
+      setCurrentTableID('');
+    } else {
+      const formItem = generateItem(item);
+      handleChangeList([...formList, formItem]);
+    }
+    setAddToTable(false);
+    setVisible(false);
+  };
+
+  const findFormItem = () => {
+    const formItem = formList.find(item => item.props.id === selectList[0]);
+
+    if (formItem?.isTable) {
+      // 如果是表格的话,还要寻找内部的被点击的col
+      if (selectList.length === 1) {
+        return formItem || null;
+      }
+      return (
+        formItem.columns.find(item => item.props.id === selectList[selectList.length - 1]) || null
+      );
+    } else {
+      return formList.find(item => item.props.id === select) || null;
+    }
+  };
+
+  const onChangeAttribute = newItemProps => {
+    const oldFormItem = findFormItem();
+    const newFormList = [];
+    if (oldFormItem.isColumn) {
+      // 找到表格和col然后改掉属性
+      for (const item of formList) {
+        if (item.isTable) {
+          for (const column of item.columns) {
+            if (column.props.id === selectList[selectList.length - 1]) {
+              column.props = { ...column.props, ...newItemProps };
+            }
+          }
+        }
+        newFormList.push(item);
+      }
+    } else {
+      for (const item of formList) {
+        if (item.props.id === select) {
+          item.props = { ...item.props, ...newItemProps };
+        }
+        newFormList.push(item);
+      }
+    }
+    handleChangeList(newFormList);
+  };
+
+  // 表格列变化时(新增,调整顺序)
+  const handleTableColumnChange = (id, newCole = []) => {
+    if (newCole.length) {
+      // 调整col顺序
+      const tableItem = formList.find(item => item.props.id === id);
+      tableItem.columns = newCole;
+      const newFormList = [];
+      for (const item of formList) {
+        if (item.props.id !== currentTableID) {
+          newFormList.push(item);
+        } else {
+          newFormList.push(tableItem);
+        }
+      }
+      handleChangeList(newFormList);
+    } else {
+      setCurrentTableID(id);
+      setAddToTable(true);
+    }
+  };
+
+  const handleFormContentSelect = ids => {
+    setSelectList(ids);
+    setSelect(ids[0]);
+  };
+
   useEffect(() => {
     if (value instanceof Array) {
       setFormList([...value]);
@@ -56,14 +149,41 @@ function AuditForm(props) {
           marginTop: 20,
         }}
       >
-        <FormContent onSelect={setSelect} onChange={handleChangeList} list={formList}></FormContent>
-        <ItemAttribute item={formList[select]} onChange={onChangeAttribute}></ItemAttribute>
+        <FormContent
+          onSelect={handleFormContentSelect}
+          onChange={handleChangeList}
+          onTableColumnChange={handleTableColumnChange}
+          list={formList}
+        />
+        <ItemAttribute
+          formList={formList}
+          key={selectList[selectList.length - 1]}
+          item={findFormItem()}
+          onFormulaClick={() => {
+            setForVisible(true);
+          }}
+          onChange={onChangeAttribute}
+        />
       </div>
       <ComponentLibrary
+        addToTable={addToTable}
         onOk={handleAddItem}
-        visible={visible}
-        onCancel={() => setVisible(false)}
-      ></ComponentLibrary>
+        visible={visible || addToTable}
+        onCancel={() => {
+          setVisible(false);
+          setAddToTable(false);
+        }}
+      />
+      <FormulaModal
+        item={formList.find(item => item.props.id === select)}
+        numFiledList={formList.filter(item => item.componentName === 'NumberField')}
+        visible={forVisible}
+        onCancel={() => setForVisible(false)}
+        onChange={formula => {
+          setForVisible(false);
+          onChangeAttribute(formula);
+        }}
+      />
     </div>
   );
 }

+ 49 - 0
src/components/DDComponents/CodeFiled/index.js

@@ -0,0 +1,49 @@
+import React, { useEffect, useState } from 'react';
+import { connect } from 'dva';
+import { Input } from 'antd';
+import { queryContractCode } from '@/services/contract';
+
+function CodeField(props) {
+  const { depId, onChange, depUserTree } = props;
+  const [value, setValue] = useState('');
+
+  const getContractCode = async params => {
+    const res = await queryContractCode(params).catch(err => console.log(err));
+    if (res.code === 200 && res.data && res.data.code) {
+      setValue(res.data.code);
+      onChange(res.data.code);
+    }
+  };
+
+  const getDepItemById = id => {
+    const fun = list => {
+      for (let i = 0; i < list.length; i++) {
+        const item = list[i];
+        if (Number(item.ID) === Number(id)) {
+          return item;
+        }
+        if (item.children?.length > 0) {
+          const res = fun(item.children);
+          if (res) return res;
+        }
+      }
+    };
+    return fun(depUserTree);
+  };
+
+  useEffect(() => {
+    if (!depId || !depUserTree.length) return;
+    const dep_code = getDepItemById(depId)?.Code;
+    const company = depUserTree.find(item => item.Flag === 1);
+    const params = {
+      company_id: company?.ID,
+      company_code: company?.Code,
+      dep_code,
+    };
+    getContractCode(params);
+  }, [depId, depUserTree]);
+
+  return <Input value={value} placeholder="选择部门后自动生成" disabled />;
+}
+
+export default connect(({ user }) => ({ depUserTree: user.depUserTree }))(CodeField);

+ 1 - 0
src/components/DDComponents/DDAttachment/index.js

@@ -1,6 +1,7 @@
 import React, { useState, useEffect } from 'react';
 import { Upload, Button, message } from 'antd';
 import { PlusOutlined } from '@ant-design/icons';
+import AliyunOSSUpload from '@/components/OssUpload/AliyunOssUploader';
 
 const CORP_ID = 'ding0cdce2d5dbf986d9';
 const AGENT_ID = '1788653353';

+ 8 - 2
src/components/DDComponents/DDDateField/index.js

@@ -1,14 +1,20 @@
 import React from 'react';
 import { DatePicker } from 'antd';
+import moment from 'moment';
 
 function DDDateField(props) {
-  const { format = '', disabled, onChange } = props;
+  const { format = '', disabled, defaultValue, onChange } = props;
 
   const handleChange = date => {
-    onChange?.(date.format('YYYY-MM-DD'));
+    if (date) {
+      onChange?.(date.format('YYYY-MM-DD HH:mm:ss'), props.id, props.label);
+    }
+    onChange?.(date, props.id, props.label);
   };
+
   return (
     <DatePicker
+      defaultValue={defaultValue !== undefined ? moment(defaultValue[0]) : null}
       disabled={disabled}
       format={format.replace('yyyy', 'YYYY').replace('dd', 'DD')}
       onChange={handleChange}

+ 1 - 1
src/components/DDComponents/DDSelectField/index.js

@@ -12,7 +12,7 @@ function DDSelectField(props) {
       disabled={disabled}
       defaultValue={defaultValue}
       onChange={value => {
-        onChange(String(value));
+        onChange(String(value), props.id, props.label);
       }}
     >
       {options?.map(cur => {

+ 7 - 0
src/components/DDComponents/DIYTable/index.css

@@ -0,0 +1,7 @@
+.hidden {
+  display: none;
+}
+
+.p-8 {
+  padding: 10px !important;
+}

+ 231 - 0
src/components/DDComponents/DIYTable/index.tsx

@@ -0,0 +1,231 @@
+import { PlusOutlined } from '@ant-design/icons';
+import { Button, Input, Table } from 'antd';
+import { ColumnsType } from 'antd/lib/table';
+import React, { useState, useEffect } from 'react';
+import DDDateField from '@/components/DDComponents/DDDateField';
+import DDSelectField from '@/components/DDComponents/DDSelectField';
+import NumberField from '@/components/DDComponents/NumberField';
+import TextNote from '@/components/DDComponents/TextNote';
+import './index.css';
+
+interface IProps {
+  table?: any; // 整个表格
+  columns?: any[]; // 当前列配置
+  onChange?: (e?: any, id?: string, label?: string) => void; // 表格修改后的值
+  displayOnly?: boolean; // 是否仅用于展示
+}
+
+interface TableDataType {
+  index: number;
+  id?: string;
+  col1?: any;
+  col2?: any;
+  col3?: any;
+  col4?: any;
+  col5?: any;
+}
+
+function DIYTable(props: IProps) {
+  const { table, columns, displayOnly, onChange } = props;
+
+  // table数据
+  const [tableData, setTableData] = useState<TableDataType[]>([{ index: 1 }]);
+
+  // table列配置
+  const tableColumnDef = [
+    {
+      title: '序号',
+      dataIndex: 'index',
+      className: 'hidden',
+    },
+  ];
+
+  // 表单填写时的表格生成
+  const handleGenerateTable = () => {
+    if (columns !== undefined && columns.length) {
+      for (let index = 0; index < columns.length; index++) {
+        const column = columns[index];
+        const columnID = column.props.id;
+        const columnLabel = column.props.label;
+        const colDef: any = {
+          dataIndex: `col${index + 1}`,
+          title: columnLabel || column.name,
+          className: 'p-8',
+        };
+        switch (column.componentName) {
+          case 'DDSelectField':
+            colDef.render = (_: any, __: any, rowIndex: number) => {
+              const id = `${rowIndex},${index};${columnID}>${table.props.id}`;
+              return (
+                // eslint-disable-next-line react/jsx-filename-extension
+                <DDSelectField
+                  id={id}
+                  label={`${columnLabel}>${table.props.label}`}
+                  style={{ padding: '0', margin: '0' }}
+                  options={column.props.options}
+                  disabled={column.props.disabled}
+                  onChange={onChange}
+                />
+              );
+            };
+            break;
+          case 'DDDateField':
+            colDef.render = (_: any, __: any, rowIndex: number) => {
+              const id = `${rowIndex},${index};${columnID}>${table.props.id}`;
+              return (
+                <DDDateField
+                  id={id}
+                  label={`${columnLabel}>${table.props.label}`}
+                  key={`${index + rowIndex}`}
+                  style={{ padding: '0', margin: '0' }}
+                  placeholder={column.props.placeholder}
+                  format={column.props.format}
+                  disabled={column.props.disabled}
+                  onChange={onChange}
+                />
+              );
+            };
+            break;
+          case 'NumberField':
+            colDef.render = (_: any, __: any, rowIndex: number) => {
+              const id = `${rowIndex},${index};${columnID}>${table.props.id}`;
+              return (
+                <NumberField
+                  id={id}
+                  label={`${columnLabel}>${table.props.label}`}
+                  size="small"
+                  width="50%"
+                  style={{ padding: '4px 11px' }}
+                  disabled={column.props.disabled}
+                  unit={column.props.unit}
+                  onChange={onChange}
+                />
+              );
+            };
+            break;
+          case 'TextField':
+            colDef.render = (_: any, __: any, rowIndex: number) => {
+              const id = `${rowIndex},${index};${columnID}>${table.props.id}`;
+              return (
+                <Input
+                  disabled={column.props.disabled}
+                  placeholder={column.props.placeholder}
+                  onChange={e =>
+                    onChange?.(e.target.value, id, `${columnLabel}>${table.props.label}`)
+                  }
+                />
+              );
+            };
+            break;
+          case 'TextNote':
+            colDef.title = '说明';
+            colDef.render = (_: any, __: any, rowIndex: number) => {
+              const id = `${rowIndex},${index};${columnID}>${table.props.id}`;
+              return (
+                <TextNote
+                  id={id}
+                  style={{ padding: '0', margin: '0' }}
+                  value={column.props.placeholder}
+                />
+              );
+            };
+            break;
+          default:
+            break;
+        }
+        tableColumnDef.push(colDef);
+      }
+    }
+  };
+
+  // 当仅用作展示时
+  const displayColumnDef = () => {
+    const rows = columns;
+    if (rows && rows.length) {
+      for (let index = 0; index < rows.length; index++) {
+        // 把每一行的数据提出来到一个对象里
+        if (rows) {
+          const row = rows[index];
+          if (index === 0) {
+            // 列配置
+            row.forEach((col: any) => {
+              if (col) {
+                tableColumnDef.push({
+                  title: col?.name || '',
+                  dataIndex: col?.type || '',
+                  className: '',
+                });
+              }
+            });
+          }
+        }
+      }
+    }
+  };
+
+  const handleRowChange = () => {
+    setTableData([...tableData, { index: tableData.length + 1 }]);
+  };
+
+  if (!displayOnly) {
+    handleGenerateTable();
+  } else {
+    displayColumnDef();
+  }
+
+  useEffect(() => {
+    if (columns.length === 0) {
+      return;
+    }
+    const rows = columns;
+    const newTableData = [];
+    if (rows && rows.length) {
+      if (displayOnly) {
+        for (let index = 0; index < rows.length; index++) {
+          // 把每一行的数据提出来到一个对象里
+          const row = rows[index];
+          const rowData: any = {};
+          if (displayOnly) {
+            row.forEach((col: any) => {
+              if (col) {
+                rowData.index = index + 1;
+                // eslint-disable-next-line prefer-destructuring
+                rowData[col.type] = col.value[0];
+                rowData.key = col.id + index;
+              }
+            });
+            newTableData.push(rowData);
+          }
+        }
+      } else {
+        newTableData.push({
+          index: 1,
+        });
+      }
+      setTableData(newTableData);
+    }
+  }, [columns]);
+
+  return (
+    <>
+      {table.name && !displayOnly ? table.name : null}
+      <Table
+        style={displayOnly ? { margin: '10px 24px 10px 0' } : {}}
+        columns={tableColumnDef}
+        dataSource={tableData}
+        pagination={false}
+      />
+      <Button
+        type="dashed"
+        icon={<PlusOutlined />}
+        block
+        onClick={handleRowChange}
+        style={displayOnly ? { display: 'none' } : {}}
+      >
+        新增行
+      </Button>
+    </>
+  );
+}
+
+export default DIYTable;

+ 77 - 26
src/components/DDComponents/DepartmentField/index.js

@@ -1,52 +1,103 @@
 import { TreeSelect } from 'antd';
-import React, { useState, useEffect } from 'react';
-import { queryDDdepList } from '@/services/boom';
+import React, { useState, useEffect, useMemo } from 'react';
 import { connect } from 'dva';
+// import { queryDDdepList } from '@/services/boom';
+// import { queryDDdepList } from '@/services/boom';
 
 function DepartmentField(props) {
-  const { value = [], onChange, depUserTree } = props;
-  const [treeData, setTreeData] = useState([]);
-
-  const genTreeNode = dep => {
-    return {
-      id: dep.dept_id,
-      pId: dep.parent_id,
-      value: dep.dept_id,
-      title: dep.name,
-      isLeaf: false,
-    };
-  };
+  const { value = [], onChange, defaultValue, depTrees } = props;
+  // const [treeData, setTreeData] = useState([]);
+
+  // const genTreeNode = dep => {
+  //   return {
+  //     id: dep.dept_id,
+  //     pId: dep.parent_id,
+  //     value: dep.dept_id,
+  //     title: dep.name,
+  //     isLeaf: false,
+  //   };
+  // };
+
+  // const onLoadData = async ({ id }) => {
+  //   let depList = await queryDDdepList({ dept_id: id });
 
-  const onLoadData = async ({ id }) => {
-    let depList = await queryDDdepList({ dept_id: id });
+  //   console.log(depList);
+  //   if (depList.length > 0) {
+  //     let nodes = depList.map(genTreeNode);
+  //     setTreeData([...treeData, ...nodes]);
+  //   }
+  // };
+
+  const findDepName = (list, ID) => {
+    let result = '';
+    const dep = list.find(item => item.ID === ID);
+    if (dep) {
+      result = dep.Name;
+      return result;
+    }
+    for (let index = 0; index < list.length; index++) {
+      const element = list[index];
+      if (element?.children && element?.children.length) {
+        result = findDepName(element.children, ID);
+        if (result) {
+          break;
+        }
+      }
+    }
+    return result;
+  };
 
-    console.log(depList);
-    if (depList.length > 0) {
-      let nodes = depList.map(genTreeNode);
-      setTreeData([...treeData, ...nodes]);
+  const findDepID = (list, Name) => {
+    let result = '';
+    const dep = list.find(item => item.Name === Name);
+    if (dep) {
+      result = dep.ID;
+    } else {
+      for (let index = 0; index < list.length; index++) {
+        const element = list[index];
+        if (element?.children && element?.children.length) {
+          result = findDepID(element.children, Name);
+          if (result) {
+            break;
+          }
+        }
+      }
     }
+    return result;
   };
 
-  const onChangeValue = newValue => {
-    onChange(newValue);
+  const defaultID = useMemo(() => {
+    if (defaultValue !== undefined) {
+      return findDepID(depTrees, defaultValue[0]);
+    } else {
+      return null;
+    }
+  }, [defaultValue]);
+
+  const onChangeValue = (newValue, label) => {
+    // const depName = findDepName(depTrees, newValue);
+    onChange(label);
   };
 
   return (
     <TreeSelect
       showSearch
-      multiple
+      // // multiple
       allowClear
-      defaultValue={value}
+      defaultValue={defaultID}
       dropdownStyle={{
         maxHeight: 400,
         overflow: 'auto',
       }}
       style={{ width: '100%' }}
       placeholder="请选择部门"
-      treeData={depUserTree}
+      treeData={depTrees}
+      filterTreeNode={(input, option) => {
+        return option.title.toLowerCase().includes(input.toLowerCase());
+      }}
       onChange={onChangeValue}
     />
   );
 }
 
-export default connect(({ user }) => ({ depUserTree: user.depUserTree }))(DepartmentField);
+export default connect(({ user }) => ({ depTrees: user.depTrees }))(DepartmentField);

+ 14 - 0
src/components/DDComponents/FormulaField/index.js

@@ -0,0 +1,14 @@
+import React, { useEffect, useState } from 'react';
+import { Input } from 'antd';
+
+function FormulaField(props) {
+  const { evalStr, onChange } = props;
+  useEffect(() => {
+    const value = eval(evalStr);
+    onChange?.(value);
+  }, [evalStr]);
+
+  return <Input value={eval(evalStr)} />;
+}
+
+export default FormulaField;

+ 55 - 0
src/components/DDComponents/ManufacturerField/index.js

@@ -0,0 +1,55 @@
+import React, { useState, useEffect } from 'react';
+import { Select } from 'antd';
+import { querySupplierList } from '@/services/contract';
+
+function ManufacturerField(props) {
+  const { value, disabled = false, defaultValue, onChange } = props;
+  const user = JSON.parse(localStorage.getItem('currentUser'));
+
+  const [option, setOption] = useState([]);
+  const [loading, setLoading] = useState(false);
+
+  const getSupplier = async () => {
+    setLoading(true);
+    const res = await querySupplierList({
+      project_id: 1,
+      is_super: user.IsSuper,
+      created_by: user.CName,
+      page_size: 9999,
+    }).catch(err => {
+      console.log(err);
+      setLoading(false);
+    });
+
+    if (res?.code === 200 && res?.data?.list?.length) {
+      const supplier = res?.data?.list.map(item => {
+        return { label: item.name, value: item.id };
+      });
+      setOption(supplier);
+    }
+    setLoading(false);
+  };
+
+  // 只要第一次加载即可
+  useEffect(() => {
+    getSupplier();
+  }, []);
+
+  return (
+    <Select
+      showSearch
+      loading={loading}
+      style={{ width: '100%' }}
+      disabled={disabled}
+      defaultValue={defaultValue}
+      onChange={val => {
+        const supplier = option.find(item => item.value === val).label;
+        onChange(supplier);
+      }}
+      filterOption={(input, opt) => (opt?.label ?? '').toLowerCase().includes(input.toLowerCase())}
+      options={option}
+    />
+  );
+}
+
+export default ManufacturerField;

+ 1 - 1
src/components/DDComponents/NumberField/index.js

@@ -11,7 +11,7 @@ function NumberField(props) {
       disabled={disabled}
       formatter={value => `${value}${unit || ''}`}
       onChange={e => {
-        onChange?.(String(e));
+        onChange?.(e ? String(e) : 0, props.id, props.label);
       }}
     />
   );

+ 51 - 0
src/components/DDComponents/ProjectField/index.js

@@ -0,0 +1,51 @@
+import React, { useEffect, useState } from 'react';
+import { Select } from 'antd';
+import { queryApproval } from '@/services/approval';
+
+function DDProjectField(props) {
+  const { value, disabled = false, onChange } = props;
+  const [projectList, setProjectList] = useState([]);
+  const [loading, setLoading] = useState(false);
+
+  const getProjectList = async () => {
+    setLoading(true);
+    const res = await queryApproval({ pageSize: 9999 }).catch(err => {
+      console.log(err);
+      setLoading(false);
+    });
+    if (res.code === 200) {
+      console.log(res.data.list);
+      setProjectList(res.data.list);
+      setLoading(false);
+    }
+  };
+
+  useEffect(() => {
+    getProjectList();
+  }, []);
+  return (
+    <Select
+      showSearch
+      loading={loading}
+      style={{ width: '100%' }}
+      disabled={disabled}
+      defaultValue={value ? Number(value) : undefined}
+      onChange={val => {
+        console.log(val);
+        const project = projectList.find(item => item.id === val);
+        onChange(`${val}`);
+      }}
+      filterOption={(input, option) =>
+        (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
+      }
+      options={projectList.map(item => {
+        return {
+          label: `${item.project_name}(${item.project_full_code})`,
+          value: item.id,
+        };
+      })}
+    />
+  );
+}
+
+export default DDProjectField;

+ 9 - 0
src/components/DDComponents/TextNote/index.js

@@ -0,0 +1,9 @@
+import React from 'react';
+
+function TextNote(props) {
+  const { value } = props;
+
+  return <div>{value}</div>;
+}
+
+export default TextNote;

+ 88 - 42
src/components/DDComponents/index.js

@@ -1,21 +1,28 @@
-import {Input, InputNumber, Select, DatePicker, Rate} from 'antd';
+import React from 'react';
+import { Input, InputNumber, Select, DatePicker, Rate } from 'antd';
 import TableField from './TableField';
 import PhoneField from './PhoneField';
 import InnerContactField from './InnerContactField';
 import DepartmentField from './DepartmentField';
+import CodeField from './CodeFiled';
 import DDMultiSelectField from './DDMultiSelectField';
 import NumberField from './NumberField';
 import DDPhotoField from './DDPhotoField';
 import DDSelectField from './DDSelectField';
 import DDDateField from './DDDateField';
 import DDDateRangeField from './DDDateRangeField';
-import DDAttachment from './DDAttachment';
+// import DDAttachment from './DDAttachment';
+import FormulaField from './FormulaField';
+import DIYTable from './DIYTable/index.tsx';
+import TextNode from './TextNote';
+import DDProjectField from './ProjectField';
+import ManufacturerField from './ManufacturerField';
 
-const {Option} = Select;
-const {RangePicker} = DatePicker;
+const { Option } = Select;
+const { RangePicker } = DatePicker;
 
 export default function DDComponents(props) {
-  const {item, onChange} = props;
+  const { depId = '', evalStr = '', item, onChange } = props;
   const {
     id,
     label,
@@ -47,7 +54,7 @@ export default function DDComponents(props) {
   } = item.props;
   let component = null;
   switch (item.componentName) {
-    case 'TextField': //单行输入
+    case 'TextField': // 单行输入
       component = (
         <Input
           defaultValue={defaultValue}
@@ -57,7 +64,7 @@ export default function DDComponents(props) {
         />
       );
       break;
-    case 'TextareaField': //多行输入
+    case 'TextareaField': // 多行输入
       component = (
         <Input.TextArea
           defaultValue={defaultValue}
@@ -67,60 +74,99 @@ export default function DDComponents(props) {
         />
       );
       break;
-    case 'NumberField': //数字输入
-      component = <NumberField defaultValue={defaultValue} disabled={disabled} unit={unit} onChange={onChange}/>;
+    case 'NumberField': // 数字输入
+      component = (
+        <NumberField
+          defaultValue={defaultValue}
+          disabled={disabled}
+          unit={unit}
+          onChange={onChange}
+        />
+      );
       break;
-    case 'DDSelectField': //单选框
-      component =
-        <DDSelectField defaultValue={defaultValue} options={options} onChange={onChange} disabled={disabled}/>;
+    case 'DDSelectField': // 单选框
+      component = (
+        <DDSelectField
+          defaultValue={defaultValue}
+          options={options}
+          onChange={onChange}
+          disabled={disabled}
+        />
+      );
       break;
-    case 'DDMultiSelectField': //多选框
-      component = <DDMultiSelectField disabled={disabled} options={options} onChange={onChange}/>;
+    case 'DDMultiSelectField': // 多选框
+      component = <DDMultiSelectField disabled={disabled} options={options} onChange={onChange} />;
       break;
-    case 'DDDateField': //日期控件
-      component = <DDDateField format={format} disabled={disabled} onChange={onChange}/>;
+    case 'DDDateField': // 日期控件
+      component = (
+        <DDDateField
+          defaultValue={defaultValue}
+          format={format}
+          disabled={disabled}
+          onChange={onChange}
+        />
+      );
       break;
-    case 'DDDateRangeField': //时间区间控件
-      component = <DDDateRangeField format={format} disabled={disabled} onChange={onChange}/>;
+    case 'DDDateRangeField': // 时间区间控件
+      component = <DDDateRangeField format={format} disabled={disabled} onChange={onChange} />;
       break;
-    case 'TextNote': //文本说明控件
-      console.info('文本说明控件!');
-      console.log(item);
+    case 'TextNote': // 文本说明控件
+      component = <TextNode value={placeholder} />;
       break;
-    case 'PhoneField': //电话控件
-      component = <PhoneField onChange={onChange}/>;
+    case 'PhoneField': // 电话控件
+      component = <PhoneField onChange={onChange} />;
       break;
-    case 'DDPhotoField': //图片控件
-      component = <DDPhotoField/>;
+    case 'DDPhotoField': // 图片控件
+      component = <DDPhotoField />;
       break;
-    case 'MoneyField': //金额控件
-      component = <Input defaultValue={defaultValue} placeholder={placeholder} onChange={onChange}/>;
+    case 'MoneyField': // 金额控件
+      component = (
+        <Input defaultValue={defaultValue} placeholder={placeholder} onChange={onChange} />
+      );
       break;
-    case 'TableField': //明细控件
-      component = <TableField item={item}/>;
+    case 'TableField': // 明细控件
+      component = <TableField item={item} />;
       break;
-    case 'DDAttachment': //附件
+    case 'DDAttachment': // 附件
       // component = <DDAttachment />
       // component = '附件控件未渲染!'
       console.info('附件控件未渲染!');
       break;
-    case 'InnerContactField': //联系人控件
-      component = <InnerContactField onChange={onChange}></InnerContactField>;
+    case 'InnerContactField': // 联系人控件
+      component = <InnerContactField onChange={onChange} />;
+      break;
+    case 'DepartmentField': // 部门控件
+      component = <DepartmentField defaultValue={defaultValue} onChange={onChange} />;
       break;
-    case 'DepartmentField': //部门控件
-      component = <DepartmentField onChange={onChange}/>;
+    case 'CodeField': // 合同编号控件
+      component = <CodeField depId={depId} onChange={onChange} />;
       break;
-    case 'RelateField': //关联审批单
-      component = '关联审批单控件未渲染!'
+    case 'ProjectField': // 项目控件
+      component = <DDProjectField value={defaultValue} disabled={disabled} onChange={onChange} />;
       break;
-    case 'AddressField': //省市区控件
-      component = '省市区控件未渲染!'
+    case 'DIYTable': // 可自定义表格控件
+      component = <DIYTable table={item} columns={item.columns} onChange={onChange} />;
       break;
-    case 'StarRatingField': //评分控件
-      component = '评分控件未渲染!'
+    case 'FormulaField':
+      component = <FormulaField evalStr={evalStr} onChange={onChange} />;
       break;
-    case 'FormRelateField': //关联控件
-      component = '关联控件未渲染!'
+    case 'ManufacturerField':
+      component = <ManufacturerField defaultValue={defaultValue} onChange={onChange} />;
+      break;
+    case 'RelateField': // 关联审批单
+      component = '关联审批单控件未渲染!';
+      break;
+    case 'AddressField': // 省市区控件
+      component = '省市区控件未渲染!';
+      break;
+    case 'StarRatingField': // 评分控件
+      component = '评分控件未渲染!';
+      break;
+    case 'FormRelateField': // 关联控件
+      component = '关联控件未渲染!';
+      break;
+
+    default:
       break;
   }
 

+ 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 && (

+ 17 - 1
src/models/user.js

@@ -38,6 +38,17 @@ function getDepUserTree(data) {
   return data;
 }
 
+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;
+}
 const getRoleList = data => {
   let roleList = [];
   (data || []).forEach(dep => {
@@ -63,6 +74,7 @@ export default {
     depRole: [],
     depUserTree: [],
     roleList: [],
+    depTrees: [],
   },
 
   effects: {
@@ -167,12 +179,16 @@ export default {
     *fetchDepV2({ payload, callback }, { call, put }) {
       const response = yield call(queryDepV2, { pageSize: 999999 });
       if (response) {
+        let temp = JSON.parse(JSON.stringify(response.data.list));
         const depUserTree = response.data.list.map(item => {
           return getDepUserTree(item);
         });
+        const depTrees = temp.map(item => {
+          return getDepTreeData(item);
+        });
         yield put({
           type: 'saveState',
-          payload: { depUserTree },
+          payload: { depUserTree, depTrees },
         });
       }
     },

+ 81 - 89
src/pages/Detail/AuditDetailed.js

@@ -1,46 +1,35 @@
 import DDComponents from '@/components/DDComponents';
-import React, {useMemo, useState} from 'react';
-import {Form} from '@ant-design/compatible';
-import '@ant-design/compatible/assets/index.css';
-
-const layout = {
-  labelCol: {
-    span: 8,
-  },
-  wrapperCol: {
-    span: 16,
-  },
-};
+import React, { useMemo, useState } from 'react';
+import { Button, Form } from 'antd';
+import { FormulaType } from '@/components/AuditForm/FormulaModal';
 
 const AuditDetailed = props => {
-  // const [form] = Form.useForm();
-  const {items, form} = props;
+  const { allValues = [], items, form, onValuesChange, onTableValChange } = props;
+
+  const depId = useMemo(() => {
+    const id = items.find(item => item.componentName === 'DepartmentField')?.props.id;
+    const value = allValues.find(item => item.id === id)?.value;
+    if (value) return value[0];
+  }, [allValues, items]);
 
-  const behavior = useMemo(() => {
-    let data = {};
+  const data = useMemo(() => {
+    let linkedData = {};
     items.forEach(d => {
       const item = d.props;
-      if (item.behaviorLinkage) {
-        const key = item.id;
-        const options = item.options.map(o => {
-          let data;
-          try {
-            data = JSON.parse(o);
-          } catch (error) {
-            data = {key: o, value: o};
-          }
-          return data;
-        });
-        item.behaviorLinkage.forEach(b => {
-          const value = b.value;
-          b.targets.forEach(t => {
-            data[t.fieldId] = {key, value: options.find(o => o.key == value)?.value};
-          });
-        });
+      if (item.linked) {
+        linkedData = { ...linkedData, [item.id]: item.linked };
       }
     });
 
-    return data;
+    const linkedList =
+      items
+        ?.map(item => {
+          const linked = item.props.linked;
+          return linked ? Object.values(linked).flat() : [];
+        })
+        .flat() || [];
+
+    return { linkedData, linkedList };
   }, [items]);
 
   const onFinish = values => {
@@ -48,51 +37,25 @@ const AuditDetailed = props => {
   };
 
   const GetComponent = item => {
-    const {
-      id,
-      label,
-      bizAlias,
-      required,
-      placeholder,
-      options,
-      align,
-      statField,
-      hideLabel,
-      objOptions,
-      format,
-      pushToAttendance,
-      labelEditableFreeze,
-      requiredEditableFreeze,
-      unit,
-      extract,
-      link,
-      payEnable,
-      bizType,
-      childFieldVisible,
-      notPrint,
-      verticalPrint,
-      hiddenInApprovalDetail,
-      disabled,
-      notUpper,
-      children, // 子控件
-    } = item.props;
-    // 判断是否属于关联项
-    if (behavior[id]) {
-      const {key, value} = behavior[id];
-      let currentValue = form.getFieldValue(key);
-      try {
-        currentValue = JSON.parse(currentValue);
-      } catch (error) {
-      }
-      // 判断是否需要渲染
-      if (currentValue instanceof Array) {
-        if (currentValue?.indexOf(value) == -1) return null;
-      } else {
-        if (currentValue != value) return null;
-      }
+    const { id, label, bizAlias, required, notUpper } = item.props;
+    // 判断是否关联项
+    if (data.linkedList.findIndex(curId => curId == id) !== -1) {
+      let control = null; // 当前空间是否显示的条件 当id为control.id的组件选择的选项值为control.value 时显示
+      Object.keys(data.linkedData).forEach(ctlIs => {
+        const linked = data.linkedData[ctlIs];
+        Object.keys(linked).forEach(value => {
+          const ids = linked[value];
+          if (ids.findIndex(curId => curId == id) !== -1) {
+            control = { id: ctlIs, value };
+          }
+        });
+      });
+      let currentValue = form.getFieldValue(control?.id);
+      if (currentValue != control?.value) return null;
     }
+
     let formLabel;
-    if (bizAlias) {
+    if (bizAlias !== undefined) {
       formLabel = bizAlias;
     } else {
       try {
@@ -103,26 +66,55 @@ const AuditDetailed = props => {
       }
     }
 
-    const component = DDComponents({item});
-    if (!component) return null;
+    const renderComponents = () => {
+      let content = '';
+      if (item.componentName === 'CodeField') {
+        content = <DDComponents item={item} depId={depId} />;
+      } else if (item.componentName === 'FormulaField') {
+        const strList = item.props?.formula?.map(formu => {
+          if (formu.type === FormulaType.Filed) {
+            const numItem = allValues?.find(item => item.id == formu.id);
+            return numItem?.value[0] || 0;
+          }
+          return formu.label;
+        });
+        const evalStr = strList?.join('');
+        content = <DDComponents item={item} evalStr={evalStr} />;
+      } else {
+        content = <DDComponents item={item} />;
+      }
+      return content;
+    };
+
+    // const component = DDComponents({ item });
+    // if (!component) return null;
     return (
-      <Form.Item label={formLabel}>
-        {typeof component == 'string'
-          ? component
-          : form.getFieldDecorator(id, {
-            rules: [{required}],
-            initialValue: item.props.defaultValue
-          })(component)}
-        {notUpper == 1 && <p>大写</p>}
-      </Form.Item>
+      <>
+        {item?.isTable === undefined ? (
+          <Form.Item
+            key={id}
+            name={id}
+            label={formLabel}
+            rules={[{ required }]}
+            initialValue={item?.props?.defaultValue}
+          >
+            {renderComponents()}
+          </Form.Item>
+        ) : (
+          <DDComponents item={item} onChange={onTableValChange} />
+        )}
+      </>
     );
   };
 
   return (
     <Form
-      style={{height: '400px', overflowY: 'scroll', paddingRight: 20}}
+      form={form}
+      style={{ minHeight: '25vh', overflowY: 'auto', paddingRight: 20 }}
       layout="vertical"
       autoComplete="off"
+      onValuesChange={onValuesChange}
+      onFinish={onFinish}
     >
       {items.map(item => GetComponent(item))}
     </Form>

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

File diff suppressed because it is too large
+ 537 - 331
src/pages/Detail/CommitAuditModal.js


+ 1 - 2
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);
@@ -24,4 +23,4 @@ function CurrentInfo(props) {
   );
 }
 
-export default connect(({ list }) => ({ projectList: list?.project?.list || [] }))(CurrentInfo);
+export default connect(({ detail }) => ({ projectList: detail.project.list }))(CurrentInfo);

+ 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}

+ 55 - 11
src/pages/Detail/FormAndFilesNode.js

@@ -1,10 +1,12 @@
 import { Card, Col, Input, Row, Modal, Empty, Collapse } from 'antd';
 import { Form } from '@ant-design/compatible';
 import { useForm } from 'antd/lib/form/Form';
-import { useMemo, useState } from 'react';
-import CommentContent from '@/components/CommentContent';
+import React, { useMemo, useState, useEffect } from 'react';
 import { connect } from 'dva';
+import CommentContent from '@/components/CommentContent';
 import AttachmentTable from '@/components/AttachmentTable';
+import DIYTable from '@/components/DDComponents/DIYTable';
+import { queryApproval } from '@/services/approval';
 
 const { confirm } = Modal;
 const { Panel } = Collapse;
@@ -12,8 +14,23 @@ const { Panel } = Collapse;
 const FormAndFilesNode = props => {
   const { formData, excelFileList, comment, version, loading } = props;
 
+  const [projectList, setProjectList] = useState([]);
+
+  const getProjectList = async () => {
+    const res = await queryApproval({ pageSize: 9999 }).catch(err => {
+      console.log(err);
+    });
+    if (res.code === 200) {
+      setProjectList(res.data.list);
+    }
+  };
+
+  useEffect(() => {
+    getProjectList();
+  }, []);
+
   const FormContent = useMemo(() => {
-    return renderFrom(formData);
+    return renderFrom(formData, projectList);
   }, [formData]);
 
   if (formData) {
@@ -28,32 +45,58 @@ const FormAndFilesNode = props => {
         </Row>
       </Card>
     );
-  } else if (excelFileList?.length > 0) {
+  }
+
+  if (excelFileList?.length > 0) {
     return (
       <Card title="附件信息">
         <AttachmentTable version={version} canDelete={version.last_version == 0} />
       </Card>
     );
-  } else {
-    return null;
   }
+  return null;
 };
 
-const renderFrom = data => {
+const renderFrom = (data, projects) => {
   if (!data) return <Empty description="没有表单信息" />;
   try {
     const ding_schema = JSON.parse(data)[0];
     const formData = JSON.parse(ding_schema)?.formComponentValues;
+    formData.forEach(item => {
+      if (item.type === 'DIYTable') {
+        item.value = item.value.map(row => JSON.parse(row));
+      }
+    });
     if (formData.length == 0) return <Empty description="没有表单信息" />;
     return (
       <>
         {formData.map((item, idx) => {
-          const value = item.value.join(',');
+          if (item.type === 'DIYTable') {
+            return (
+              <Form.Item
+                key={`FormAndFilesNode_${item.id}`}
+                labelCol={{ span: 5 }}
+                wrapperCol={{ span: 19 }}
+                label={item.name}
+              >
+                <DIYTable key={item.id} table={item} columns={item.value} displayOnly />
+              </Form.Item>
+            );
+          }
+
+          let value = item.value.join(',');
+          if (item.id.includes('ProjectField')) {
+            if (value) {
+              value = Number(value);
+              const project = projects.find(item => item.id === value);
+              value = `${project.project_name}(${project.project_full_code})`;
+            }
+          }
           return (
             <Form.Item
-              key={`FormAndFilesNode_${idx}`}
-              labelCol={{ span: 4 }}
-              wrapperCol={{ span: 14 }}
+              key={`FormAndFilesNode_${item.id}`}
+              labelCol={{ span: 5 }}
+              wrapperCol={{ span: 18 }}
               label={item.name}
             >
               <div
@@ -63,6 +106,7 @@ const renderFrom = data => {
                   backgroundColor: '#ececef',
                   border: '1px solid #bcb9b9',
                   borderRadius: '4px',
+                  minHeight: '40px',
                 }}
               >
                 {value}

+ 30 - 13
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';
@@ -21,6 +18,7 @@ import MergeModal from './MergeModal';
 import { GetTokenFromUrl, getToken } from '@/utils/utils';
 import {
   getAuditDetail,
+  getVersionDetail,
   queryDelSheetRecord,
   queryDetail,
   queryDingInstanceExecute,
@@ -36,9 +34,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 {
@@ -118,6 +114,13 @@ function Detail(props) {
     localStorage['JWT-TOKEN'] = TOKEN;
   }
 
+  const node = useMemo(() => {
+    if (version.template_node_id && flowDetail.nodes.length > 0) {
+      return flowDetail.nodes.find(item => item.Id == version.template_node_id);
+    }
+    return null;
+  }, [flowDetail, version.template_node_id]);
+
   const flow = useMemo(() => {
     let data = {
       active: 0,
@@ -140,6 +143,7 @@ function Detail(props) {
     }
     return data;
   }, [auditList, version]);
+
   const active_audit = flow.active_audit;
   const isAuditor = useMemo(() => {
     return active_audit == 1 && flow.currentNode?.auditor == currentUser.ID;
@@ -273,7 +277,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 +286,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 +297,7 @@ function Detail(props) {
             id: templateId,
           },
         });
-        if (audit_status == 3) {
+        if (data.audit_status == 3) {
           // 更新审批流
           dispatch({
             type: 'detail/queryAuditList',
@@ -401,12 +404,12 @@ function Detail(props) {
 
   const changeVersion = async id => {
     let version;
-    if (typeof id == 'object') {
+    if (typeof id === 'object') {
       version = id;
       localStorage.excelId = version.id;
       localStorage.excelItem = JSON.stringify(version);
     } else {
-      version = await getAuditDetail({ userId: currentUser.ID, excelID });
+      version = await getVersionDetail({ excelID });
       if (!version) {
         const excelId = localStorage.excelItem
           ? JSON.parse(localStorage.excelItem)
@@ -442,6 +445,14 @@ function Detail(props) {
     }
   };
 
+  // console.log('0--------------', currentUser);
+
+  //是否展示psr表上面的按钮
+  const showPsrBtns = useMemo(() => {
+    if (node?.node_type_psr == 3 || node?.node_type_psr == 4) return true;
+    return false;
+  }, [node]);
+
   const getUser = newUser => {
     try {
       if (JSON.stringify(newUser) != JSON.stringify(userRef.current)) {
@@ -493,6 +504,9 @@ function Detail(props) {
         project_id: projectId,
       },
     });
+    dispatch({
+      type: 'detail/queryProject',
+    });
 
     // dispatch({
     //   type: 'detail/queryListParentByUser',
@@ -589,6 +603,7 @@ function Detail(props) {
           onChange={e => exportExcl(e.target.files)}
         />
       </div>
+      {showPsrBtns && <PsrControl sheetRef={sheetRef} />}
       <div style={{ display: 'flex' }}>
         <div
           className={styles.content}
@@ -614,8 +629,10 @@ function Detail(props) {
                   className={styles.sheet}
                   ref={sheetRef}
                   onClickCell={onClickCell}
+                  permissions={currentUser.Permission}
                   version={version}
                   templateId={templateId}
+                  node={node}
                   getUser={getUser}
                   onUpdate={onUpdate}
                   onDelSheet={onDelSheet}
@@ -625,7 +642,7 @@ function Detail(props) {
           )}
 
           <FormAndFilesNode
-            formData={version?.formStr}
+            formData={version?.formStr || version?.ding_schema}
             excelFileList={excelFileList}
             version={version}
           />

+ 146 - 4
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,16 @@ 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,
+      node,
+    } = this.props;
     const data = currentData || this.props.data;
     const _this = this;
     if (!this.luckysheet) {
@@ -82,6 +92,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 => {
@@ -129,6 +140,11 @@ class LuckySheet extends React.Component {
             this.luckysheet.setCellFormat(0, 0, 'bg', '#fff');
           }, 100);
         },
+        workbookCreateAfter: options => {
+          setTimeout(() => {
+            this.luckysheet.refreshFormula();
+          }, 800);
+        },
       },
     };
     if (version) {
@@ -149,6 +165,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()));
@@ -169,7 +186,8 @@ class LuckySheet extends React.Component {
         option.enableAddBackTop = false;
         option.showsheetbarConfig = {
           add: false,
-          sheet: false,
+          menu: false,
+          // sheet: false,
         };
         option.cellRightClickConfig = {
           copy: false, // 复制
@@ -208,12 +226,18 @@ 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: '当前清单不可编辑!',
         };
         unableEdit(option);
+      } else if (node?.node_type_psr == 1 ||node?.node_type_psr == 2 || node?.node_type_psr == 5 ) {
+        option.authority = {
+          sheet: true,
+          hintText: '流程已完结,不可编辑!',
+        };
+        unableEdit(option);
       }
     } else if (data && data.length > 0) {
       option.data = JSON.parse(JSON.stringify(data));
@@ -246,7 +270,9 @@ class LuckySheet extends React.Component {
     // 所以默认设置第一个单元格的背景色
     setTimeout(() => {
       this.luckysheet.setCellFormat(0, 0, 'bg', '#fff');
+      this.luckysheet.refreshFormula();
     }, 500);
+    
   }
 
   // componentDidUpdate(prevProps) {
@@ -614,6 +640,37 @@ class LuckySheet extends React.Component {
     return comment;
   }
 
+  async goalSeek(type, goal, setting) {
+    const luckysheet = this.luckysheet;
+    const sheet = this.luckysheet.getSheet({
+      name: '毛利概算',
+    });
+    const order = sheet.order;
+
+    try {
+      let defaultValue = luckysheet.getCellValue(9, 2, {
+        order,
+      });
+      const result = await GoalSeek({
+        goal,
+        fn: fn,
+        fnParams: [defaultValue, sheet.data, type],
+        maxIterations: 1000,
+        independentVariableIdx: 0,
+        ...setting,
+      });
+      luckysheet.setCellValue(9, 2, result, {
+        order,
+      });
+      console.log(result)
+      luckysheet.refreshFormula()
+      message.success("计算成功")
+    } catch (error) {
+      message.error("计算失败")
+      console.log(error);
+    }
+  }
+
   render() {
     return (
       <iframe
@@ -626,5 +683,90 @@ class LuckySheet extends React.Component {
     );
   }
 }
+function fn(C10, data, type) {
+  let C50 = data[49][2].v || 0;
+  let C49 = data[48][2].v || 0;
+  let C20 = data[19][2].v || 0;
+  let C21 = data[20][2].v || 0;
+  let C9 = data[8][2].v || 0;
+  let C15 = data[14][2].v || 0;
+  let C16 = data[15][2].v || 0;
+  let C17 = data[16][2].v || 0;
+  let C18 = data[17][2].v || 0;
+  let C19 = data[18][2].v || 0;
+  let C51 = data[50][2].v || 0;
+
+  let G42 = data[41][6].v || 0;
+  let G12 = data[11][6].v || 0;
+  let G13 = data[12][6].v || 0;
+  let G7 = data[6][6].v || 0;
+  let G8 = data[7][6].v || 0;
+  switch (type) {
+    case 1:
+      // 净利率
+      return C3() / C59();
+    case 2:
+      // 贡献毛利率
+      return C4() / C59();
+    case 3:
+      // 合同总价
+      return C59();
+  }
 
+  function C59() {
+    return C58() + C57() + C56() + C54();
+  }
+
+  function C4() {
+    return C3() + C50 + C49 + C48() + G42 + C43();
+  }
+  function C48() {
+    return C59() * G12;
+  }
+  function C58() {
+    return (C56() + C57()) * 0.12;
+  }
+  function C57() {
+    return (((C20 + C21) * C10) / (1 + G7)) * G7 - ((C20 + C21) / (1 + G8)) * G8;
+  }
+  function C56() {
+    return (
+      (((C15 + C16 + C17 + C18 + C19) * C10) / (1 + G7)) * G7 -
+      ((C15 + C16 + C17 + C18 + C19) / (1 + G7)) * G7
+    );
+  }
+  function C54() {
+    return SUM(2, [14, 20]) * C10;
+  }
+
+  function C3() {
+    return C55() - C53();
+  }
+  function C55() {
+    return C59() / (1 + G7);
+  }
+  function C53() {
+    return C52() - ((SUM(2, [14, 18]) / (1 + G7)) * G7 + (SUM(2, [19, 20]) / (1 + G8)) * G8);
+  }
+  function C52() {
+    return SUM(2, [14, 50]);
+  }
+  function C43() {
+    return C59() * G13;
+  }
+  function C44() {
+    return C59() * C9;
+  }
+
+  function SUM(x, [y1, y2]) {
+    let total = 0;
+    for (let i = y1; i < y2; i++) {
+      const item = data[i][x];
+      if (!isNaN(item.v)) {
+        total += item.v;
+      }
+    }
+    return total;
+  }
+}
 export default LuckySheet;

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

@@ -0,0 +1,89 @@
+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();
+  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);
+    let setting = null;
+    try {
+      if (type == 3) {
+        setting = {
+          customToleranceFn: res => Math.abs(res - value) < 0.001
+        };
+      } else {
+        setting = {
+          customToleranceFn: res => Math.abs(res - value) < 0.00001
+        };
+      }
+      sheetRef.current.goalSeek(type, value, setting);
+    } 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);

+ 10 - 6
src/pages/Detail/models/detail.js

@@ -1,16 +1,13 @@
 import {
   queryFlowInfo,
-  // commitSheet,
   querySheet,
   queryHistory,
-  // queryHistoryDetail,
   queryComment,
   addComment,
   createExcel,
   queryExcel,
   updateFlowInfo,
   submitAudit,
-  // approve,
   queryFiles,
   deleteFiles,
 } from '@/services/PurchaseList';
@@ -39,6 +36,7 @@ import {
   queryBindClassify,
   ChartTempOSSData,
 } from '@/services/boom';
+import { queryApproval, queryProjectListUser } from '@/services/approval';
 import { queryRole } from '@/services/SysAdmin';
 import { setCurrentUser } from '@/utils/authority';
 import { queryProjectMenu } from '@/services/SysAdmin';
@@ -82,15 +80,21 @@ export default {
     classifyList: [],
     OSSData: {},
     excelFileList: [],
+    project: {
+      list: [],
+      pagination: false,
+    },
   },
 
   effects: {
-    *queryAuthority({ payload, callback }, { call, put }) {
-      const response = yield call(queryAuthority, payload);
+    *queryProject({ callback }, { call, put }) {
+      const response = yield call(queryProjectListUser, { pageSize: 99999 });
       if (response) {
         yield put({
           type: 'save',
-          payload: { authority: response.data },
+          payload: {
+            project: { list: response.data },
+          },
         });
       }
     },

+ 13 - 81
src/pages/Flow/FlowDetail.json

@@ -69,7 +69,7 @@
       "x": -578,
       "y": 6,
       "flow_id": 9,
-      "node_type_psr": 1,
+      "node_type_psr": 0,
       "count": 0,
       "role_list": ""
     },
@@ -95,7 +95,8 @@
       "flow_id": 0,
       "node_type_psr": 0,
       "count": 0,
-      "role_list": ""
+      "role_list": "",
+      "is_seal": 1
     },
     {
       "id": "5216c5dc",
@@ -117,7 +118,7 @@
       "x": -789,
       "y": 26,
       "flow_id": 0,
-      "node_type_psr": 0,
+      "node_type_psr": 5,
       "count": 0,
       "role_list": ""
     },
@@ -262,7 +263,7 @@
       "x": 325,
       "y": -170,
       "flow_id": 5,
-      "node_type_psr": 1,
+      "node_type_psr": 0,
       "count": 0,
       "role_list": ""
     },
@@ -286,7 +287,7 @@
       "x": 111,
       "y": 6,
       "flow_id": 8,
-      "node_type_psr": 1,
+      "node_type_psr": 0,
       "count": 0,
       "role_list": ""
     },
@@ -310,7 +311,7 @@
       "x": -63,
       "y": -435,
       "flow_id": 51,
-      "node_type_psr": 1,
+      "node_type_psr": 0,
       "count": 0,
       "role_list": ""
     },
@@ -359,7 +360,7 @@
       "y": -240,
       "zIndex": 10,
       "flow_id": 3,
-      "node_type_psr": 1,
+      "node_type_psr": 0,
       "count": 0
     },
     {
@@ -383,7 +384,7 @@
       "y": -117,
       "zIndex": 10,
       "flow_id": 4,
-      "node_type_psr": 1,
+      "node_type_psr": 0,
       "count": 0
     },
     {
@@ -406,7 +407,7 @@
       "x": 168,
       "y": -117,
       "flow_id": 4,
-      "node_type_psr": 1,
+      "node_type_psr": 0,
       "count": 0,
       "role_list": ""
     },
@@ -430,7 +431,7 @@
       "x": 168,
       "y": -240,
       "flow_id": 3,
-      "node_type_psr": 1,
+      "node_type_psr": 0,
       "count": 0,
       "role_list": ""
     },
@@ -455,7 +456,7 @@
       "y": -170,
       "zIndex": 10,
       "flow_id": 5,
-      "node_type_psr": 1,
+      "node_type_psr": 0,
       "count": 0
     },
     {
@@ -479,7 +480,7 @@
       "y": -170,
       "zIndex": 10,
       "flow_id": 7,
-      "node_type_psr": 1,
+      "node_type_psr": 0,
       "count": 0
     },
     {
@@ -527,54 +528,6 @@
       "y": -435,
       "zIndex": 10,
       "flow_id": 48,
-      "node_type_psr": 2,
-      "count": 0
-    },
-    {
-      "id": "node-d825c5a0-6077-4d64-a63c-5b3cde59211b",
-      "renderKey": "custom-rect",
-      "name": "custom-rect",
-      "label": "现金流",
-      "width": 120,
-      "height": 50,
-      "ports": {
-        "items": [
-          { "group": "top", "id": "3816dfce-c9a4-4655-9ecb-ecdc6f05fe53" },
-          { "group": "right", "id": "eab92693-4051-449f-b76c-b90587d47f70" },
-          { "group": "bottom", "id": "bb9edb5b-212e-491f-ac79-2bd9096739aa" },
-          { "group": "left", "id": "f65c9954-8273-4f77-9cae-a37cbafe147e" }
-        ]
-      },
-      "isCustom": true,
-      "parentKey": "custom",
-      "x": -789,
-      "y": 129,
-      "zIndex": 10,
-      "flow_id": 0,
-      "node_type_psr": 0,
-      "count": 0
-    },
-    {
-      "id": "node-050b15a7-5bc8-4091-bcc3-d48013eacadd",
-      "renderKey": "custom-rect",
-      "name": "custom-rect",
-      "label": "过程/终版PSR",
-      "width": 120,
-      "height": 50,
-      "ports": {
-        "items": [
-          { "group": "top", "id": "c52f65b7-3fb9-4e68-b039-05ab37414983" },
-          { "group": "right", "id": "fbba2fdc-f26d-488f-aaaf-f7ead51460a9" },
-          { "group": "bottom", "id": "37d3ccbf-3b3d-4e65-bd5a-cdafc81685b8" },
-          { "group": "left", "id": "8751b4b6-25ff-4843-9db0-a6ae4b7258e4" }
-        ]
-      },
-      "isCustom": true,
-      "parentKey": "custom",
-      "x": -789,
-      "y": 232,
-      "zIndex": 10,
-      "flow_id": 0,
       "node_type_psr": 0,
       "count": 0
     }
@@ -769,27 +722,6 @@
       "target": { "cell": "b57b57c8", "port": "17ff5fe6" },
       "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
     },
-    {
-      "id": "5216c5dc:188c9b68-node-d825c5a0-6077-4d64-a63c-5b3cde59211b:3816dfce-c9a4-4655-9ecb-ecdc6f05fe53",
-      "source": { "cell": "5216c5dc", "port": "188c9b68" },
-      "target": {
-        "cell": "node-d825c5a0-6077-4d64-a63c-5b3cde59211b",
-        "port": "3816dfce-c9a4-4655-9ecb-ecdc6f05fe53"
-      },
-      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
-    },
-    {
-      "id": "node-d825c5a0-6077-4d64-a63c-5b3cde59211b:bb9edb5b-212e-491f-ac79-2bd9096739aa-node-050b15a7-5bc8-4091-bcc3-d48013eacadd:c52f65b7-3fb9-4e68-b039-05ab37414983",
-      "source": {
-        "cell": "node-d825c5a0-6077-4d64-a63c-5b3cde59211b",
-        "port": "bb9edb5b-212e-491f-ac79-2bd9096739aa"
-      },
-      "target": {
-        "cell": "node-050b15a7-5bc8-4091-bcc3-d48013eacadd",
-        "port": "c52f65b7-3fb9-4e68-b039-05ab37414983"
-      },
-      "attr": "{\"line\":{\"stroke\":\"#A2B1C3\",\"targetMarker\":{\"name\":\"block\",\"width\":12,\"height\":8},\"strokeDasharray\":[0,0],\"strokeWidth\":1}}"
-    },
     {
       "id": "c28a18d3:a06aba2c-3631eae9:0f06668a",
       "source": { "cell": "c28a18d3", "port": "a06aba2c" },

+ 3 - 3
src/pages/Flow/models/flow.js

@@ -12,7 +12,7 @@ import {
   queryProcessFlows,
   queryDefaultBindClassify,
 } from '@/services/boom';
-import { queryApproval } from '@/services/approval';
+import { queryApproval, queryProjectListUser } from '@/services/approval';
 import { queryRole } from '@/services/SysAdmin';
 import { queryProject } from '@/services/PurchaseList';
 import { message } from 'antd';
@@ -177,12 +177,12 @@ export default {
       });
     },
     *queryProject({ callback }, { call, put }) {
-      const response = yield call(queryApproval, { pageSize: 99999 });
+      const response = yield call(queryProjectListUser, { pageSize: 99999 });
       if (response) {
         yield put({
           type: 'save',
           payload: {
-            projectList: response.data.list,
+            projectList: response.data,
           },
         });
       }

+ 4 - 4
src/pages/List/models/list.js

@@ -1,6 +1,6 @@
 import { queryBindClassify, queryClassify, queryProjectRecord } from '@/services/boom';
 import { queryProject } from '@/services/PurchaseList';
-import { queryApproval } from '@/services/approval';
+import { queryApproval, queryProjectListUser, queryPurchasingList } from '@/services/approval';
 import { message } from 'antd';
 import { commitSheet, queryVersionsList, queryAuditExcel, queryAuditRecord } from '@/services/boom';
 
@@ -21,7 +21,7 @@ export default {
 
   effects: {
     *queryProjectRecord({ payload = {}, callback }, { call, put }) {
-      const { data } = yield call(queryProjectRecord, payload);
+      const { data } = yield call(queryProjectRecord, payload); //queryPurchasingList
       // let temp = {};
       // data.list.forEach(item => {
       //   if (!temp[item.template_id]) {
@@ -42,12 +42,12 @@ export default {
       });
     },
     *queryProject({ callback }, { call, put }) {
-      const response = yield call(queryApproval, { pageSize: 99999 });
+      const response = yield call(queryProjectListUser, { pageSize: 99999 });
       if (response) {
         yield put({
           type: 'save',
           payload: {
-            project: response.data,
+            project: { list: response.data },
           },
         });
       }

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

@@ -5,52 +5,40 @@ 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, {
+      project_name: 'test水厂',
+      project_full_code: 'TESTCODE',
+    });
+    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 +46,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 +68,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;

+ 8 - 1
src/services/approval.js

@@ -51,7 +51,9 @@ export async function queryAuth(data) {
 
 // 查询立项 id=&pageSize=&currentPage=&user_id=
 export async function queryApproval(data) {
-  return request(`/api/v2/approval/record?${stringify(data)}`);
+  data.stage = 2;
+  return request(`/api/v2/workload/project?${stringify(data)}`);
+  // return request(`/api/v2/approval/record?${stringify(data)}`);
 }
 
 // 提交审核
@@ -115,3 +117,8 @@ export async function deleteMember(data) {
 export async function queryVersionsByProjectId(projectId, params) {
   return request(`/purchase/bom/get-bom-list/${projectId}?${stringify(params)}`);
 }
+
+// 查询采购清单列表---带权限
+export async function queryProjectListUser(data) {
+  return request(`/api/v2/approval/record/user?${stringify(data)}`);
+}

+ 6 - 0
src/services/boom.js

@@ -505,3 +505,9 @@ export async function getAuditDetail(params) {
   );
   return res.data;
 }
+export async function getVersionDetail(params) {
+  const res = await request(
+    `/api/v1/purchase/bom/get-excel-detail?excel_id=${params.excelID}`
+  );
+  return res.data;
+}

+ 24 - 0
src/services/contract.js

@@ -0,0 +1,24 @@
+import request from '@/utils/request';
+import { stringify } from 'qs';
+
+// 计算合同编号
+export const queryContractCode = async data => {
+  return await request(`/api/contract/v1/code?${stringify(data)}`);
+};
+
+// http://localhost:8000/api/supplier/v1/supplier/list
+// http://localhost:8001/api/supplier/v1/supplier/list
+// created_by
+// end_time
+// is_super
+// page
+// page_size
+// project_id
+// start_time
+
+export const querySupplierList = async data => {
+  return await request('/api/supplier/v1/supplier/list', {
+    method: 'POST',
+    body: data,
+  });
+};

+ 121 - 0
src/utils/GoalSeek.js

@@ -0,0 +1,121 @@
+const IsNanError = TypeError('resulted in NaN');
+const FailedToConvergeError = Error('failed to converge');
+const InvalidInputsError = Error('invalid inputs');
+
+export default async function GoalSeek(options) {
+  const {
+    fn,
+    fnParams,
+    percentTolerance,
+    customToleranceFn,
+    maxIterations,
+    maxStep,
+    goal,
+    independentVariableIdx,
+  } = options;
+  if (typeof customToleranceFn !== 'function') {
+    if (!percentTolerance) {
+      throw InvalidInputsError;
+    }
+  }
+  let g;
+  let y;
+  let oldY = 0;
+  let oldGuess = fnParams[independentVariableIdx];
+  let newGuess;
+  let res;
+  let max;
+  let min;
+  const absoluteTolerance = ((percentTolerance || 0) / 100) * goal;
+  
+  async function getMin(minGuest) {
+    let copyParams = [...fnParams]
+    copyParams[independentVariableIdx] = minGuest
+    res = await fn(...copyParams);
+    if (res > goal) {
+      minGuest -= oldGuess / 2;
+      return await getMin(minGuest);
+    }
+    return minGuest;
+  }
+
+  async function getMax(maxGuest) {
+    let copyParams = [...fnParams]
+    copyParams[independentVariableIdx] = maxGuest
+    res = await fn(...copyParams);
+    if (res < goal) {
+      maxGuest += oldGuess / 2;
+      return await getMax(maxGuest);
+    }
+    return maxGuest;
+  }
+  // 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;
+    // 判断是否满足条件
+    if (typeof customToleranceFn !== 'function') {
+      if (Math.abs(y) <= Math.abs(absoluteTolerance)) return fnParams[independentVariableIdx];
+    } else {
+      if (customToleranceFn(res)) return fnParams[independentVariableIdx];
+    }
+    // 获取要改变的变量
+    oldGuess = fnParams[independentVariableIdx];
+
+    // newGuess = oldGuess + y;
+    // if (Math.abs(newGuess - oldGuess) > maxStep) {
+    //   if (newGuess > oldGuess) {
+    //     newGuess = oldGuess + maxStep;
+    //   } else {
+    //     newGuess = oldGuess - maxStep;
+    //   }
+    // }
+    // 获取最大值和最小值
+    if (y > 0) {
+      max = oldGuess;
+      if (!min) {
+        min = await getMin(oldGuess / 2);
+      }
+    } else {
+      min = oldGuess;
+      if (!max) {
+        max = await getMax(oldGuess * 2);
+      }
+    }
+    // if(oldY) {
+    //   if(y > oldY) {
+
+    //   }
+    // }
+    // oldY = y;
+    // 利用二分法查询
+    newGuess = (min + max) / 2;
+    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);
+// }

+ 5 - 1
src/utils/request.js

@@ -81,7 +81,11 @@ export default function request(url, option, jwt) {
   // console.log(API_HOST);
   const time = url.indexOf('?') > -1 ? `&time=${number}` : `?time=${number}`;
   if (url.indexOf('http://') == -1 && url.indexOf('https://') == -1) {
-    if (url.indexOf('/api/v') === -1) {
+    if (
+      url.indexOf('/api/v') === -1 &&
+      url.indexOf('/api/supplier') === -1 &&
+      url.indexOf('/api/contract') === -1
+    ) {
       url = `/api/v1${url}${time}`;
     }
     // API_HOST在config/config.js的define中配置

+ 269 - 0
src/utils/uploadExcelByUrl.js

@@ -0,0 +1,269 @@
+import LuckyExcel from 'luckyexcel';
+import { getToken } from '@/utils/utils';
+import moment from 'moment';
+window.moment = moment;
+
+const uploadExcelByUrl = (nodeType, versionId, project) => {
+  const TEMPLATE_URL =
+    'https://water-service-test.oss-cn-hangzhou.aliyuncs.com/public/bom/psr1010.xlsx';
+  // 'https://water-service-test.oss-cn-hangzhou.aliyuncs.com/public/bom/psr1009.xlsx';
+  // 'https://water-service-test.oss-cn-hangzhou.aliyuncs.com/public/bom/psr1008.xlsx';
+  // 'https://gt-digitization.oss-cn-hangzhou.aliyuncs.com/public/bom/psr0913.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, project?.project_name));
+
+        if (nodeType == 4) {
+          // 隐藏现金流表
+          excelData[3].hide = 1;
+          excelData[3].status = 0;
+          // 处理现金流
+          res.push(initActual(excelData[3], category, project));
+        }
+
+        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) {
+  if (str.length < 6) return 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, project) {
+  let actualCategory = [];
+  actual.celldata.forEach((item, i, celldata) => {
+    if (item.c == 0 && item.v?.v) {
+      // LuckyExcel会将序号转float后再次转回string,导致小数出现精度问题
+      // 序号后超过2位数的固定处理,不超过两位数的保留一位小数
+      switch (item.r) {
+        case 65:
+          item.v.v = '5.10';
+          break;
+        case 66:
+          item.v.v = '5.11';
+          break;
+        case 126:
+          item.v.v = '9.12';
+          break;
+        case 127:
+          item.v.v = '9.13';
+          break;
+        case 147:
+          item.v.v = '10.12';
+          break;
+        case 148:
+          item.v.v = '10.13';
+          break;
+        case 262:
+          item.v.v = '17.15';
+          break;
+        case 264:
+          item.v.v = '17.17';
+        case 266:
+          item.v.v = '17.19';
+          break;
+        default:
+          // 处理序号转float出现多余小数的情况
+          item.v.v = formatNumber(item.v?.v);
+          break;
+      }
+    } else if (item.c == 1 && item.r == 0) {
+      item.v.v = project?.project_name;
+    } else if (item.c == 1 && item.r == 1) {
+      item.v.v = project?.project_full_code;
+    } else if (item.c == 2) {
+      // 清单分类的总价填入对应预算列
+      // c=2 为名称列
+      let value = getCellValue(item.v);
+      // 判断该行是否为类型总列
+      if (category.hasOwnProperty(value)) {
+        // 名称后第三列为预算列
+        celldata[i + 3].v.v = category[value];
+      }
+    } else if (item.c == 12 && item.r == 5) {
+      // 设置第一个月时间
+      let start_date = moment('1900-01-01 00:00:00');
+      let end_date = moment().startOf('month');
+      item.v.v = end_date.diff(start_date, 'days');
+    }
+  });
+  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;

Some files were not shown because too many files changed in this diff