فهرست منبع

Merge branch 'dev' of http://192.168.1.41:3000/lanjianrong/management into dev

# Conflicts:
#	.vscode/settings.json
caipin 4 سال پیش
والد
کامیت
6d2e0c7523
52فایلهای تغییر یافته به همراه1779 افزوده شده و 713 حذف شده
  1. 1 0
      .gitignore
  2. 11 1
      .vscode/settings.json
  3. 3 1
      package.json
  4. 27 0
      src/assets/css/common.scss
  5. 111 98
      src/components/AuditContent/index.tsx
  6. 5 11
      src/components/OssUpload/index.tsx
  7. 1 1
      src/components/SvgIcon/index.tsx
  8. 5 4
      src/pages/Contract/Content/Income/api.ts
  9. 12 2
      src/pages/Contract/Content/Income/components/Modal/api.ts
  10. 9 8
      src/pages/Contract/Content/Income/components/Modal/index.tsx
  11. 18 33
      src/pages/Contract/Content/Income/components/TableContent/index.tsx
  12. 20 9
      src/pages/Contract/Content/Income/components/Tabs/Detail/index.tsx
  13. 8 8
      src/pages/Contract/Content/Income/components/Tabs/File/index.tsx
  14. 10 34
      src/pages/Contract/Content/Income/components/Tabs/Receivable/index.tsx
  15. 11 12
      src/pages/Contract/Content/Income/index.tsx
  16. 12 11
      src/pages/Contract/Content/Spending/api.ts
  17. 6 6
      src/pages/Contract/Content/Spending/components/Modal/api.ts
  18. 19 18
      src/pages/Contract/Content/Spending/components/Modal/index.tsx
  19. 27 41
      src/pages/Contract/Content/Spending/components/TableContent/index.tsx
  20. 21 10
      src/pages/Contract/Content/Spending/components/Tabs/Detail/index.tsx
  21. 8 8
      src/pages/Contract/Content/Spending/components/Tabs/File/index.tsx
  22. 8 8
      src/pages/Contract/Content/Spending/components/Tabs/Receivable/api.ts
  23. 17 49
      src/pages/Contract/Content/Spending/components/Tabs/Receivable/index.tsx
  24. 13 14
      src/pages/Contract/Content/Spending/index.tsx
  25. 2 9
      src/pages/Login/index.tsx
  26. 21 12
      src/pages/Quality/Content/Info/Detail/api.ts
  27. 328 0
      src/pages/Quality/Content/Info/Detail/components/Modal/index.tsx
  28. 2 0
      src/pages/Quality/Content/Info/Detail/index.module.scss
  29. 375 104
      src/pages/Quality/Content/Info/Detail/index.tsx
  30. 4 4
      src/pages/Quality/Content/List/api.ts
  31. 17 21
      src/pages/Quality/Content/List/index.tsx
  32. 6 6
      src/pages/Quality/Content/List/modal.tsx
  33. 3 5
      src/pages/Quality/List/index.tsx
  34. 1 1
      src/pages/Safe/Content/Info/Detail/api.ts
  35. 6 7
      src/pages/Safe/Content/Info/Detail/components/Modal/index.tsx
  36. 112 85
      src/pages/Safe/Content/Info/Detail/index.tsx
  37. 10 0
      src/pages/Safe/Content/Info/Summary/api.ts
  38. 80 0
      src/pages/Safe/Content/Info/Summary/columnChart.tsx
  39. 14 0
      src/pages/Safe/Content/Info/Summary/index.scss
  40. 99 26
      src/pages/Safe/Content/Info/Summary/index.tsx
  41. 46 0
      src/pages/Safe/Content/Info/Summary/pieChart.tsx
  42. 2 2
      src/pages/Safe/Content/List/index.tsx
  43. 5 2
      src/router/routes.ts
  44. 69 0
      src/store/mobx/contractPaid/index.ts
  45. 2 2
      src/store/mobx/contract/index.ts
  46. 4 2
      src/store/mobx/index.ts
  47. 33 0
      src/types/auditDetail.d.ts
  48. 46 0
      src/types/contract.d.ts
  49. 1 1
      src/types/rule.d.ts
  50. 8 4
      src/types/safe.d.ts
  51. 5 4
      src/utils/consts.ts
  52. 95 29
      src/utils/util.ts

+ 1 - 0
.gitignore

@@ -4,3 +4,4 @@ build
 dist
 /package-lock.json
 /deploy.config.js
+/.vscode

+ 11 - 1
.vscode/settings.json

@@ -4,11 +4,21 @@
     "@": "${workspaceRoot}"
   },
   // "typescript.suggest.paths": false,
+  "editor.formatOnSave": false,
+  "css.validate": false,
+  "scss.validate": false,
+  "typescript.validate.enable": false
+{
+  "editor.tabSize": 2,
+  "path-intellisense.mappings": {
+    "@": "${workspaceRoot}"
+  },
+  // "typescript.suggest.paths": false,
   "css.validate": false,
   "scss.validate": false,
   "typescript.validate.enable": false,
 
-  // 格�化�置
+  // ¸ñʽ»¯ÅäÖÃ
   "editor.formatOnSave": false,
   "[javascript]": {
     "editor.formatOnSave": true,

+ 3 - 1
package.json

@@ -174,9 +174,11 @@
     "webpack": "4.42.0",
     "webpack-dev-server": "3.11.0",
     "webpack-manifest-plugin": "2.2.0",
-    "workbox-webpack-plugin": "4.3.1"
+    "workbox-webpack-plugin": "4.3.1",
+    "@types/echarts": "^4.9.3"
   },
   "dependencies": {
+    "@ant-design/charts": "^1.0.13",
     "@ant-design/icons": "^4.2.2",
     "antd": "^4.6.4",
     "axios": "^0.21.1",

+ 27 - 0
src/assets/css/common.scss

@@ -390,6 +390,8 @@
 .pi-circle-gray {
   position: relative;
   padding-left: 14px;
+  line-height: 16px;
+  height: 16px;
   &::before {
     content: '';
     position: absolute;
@@ -405,6 +407,8 @@
 .pi-circle-yellow {
   position: relative;
   padding-left: 14px;
+  line-height: 16px;
+  height: 16px;
   &::before {
     content: '';
     position: absolute;
@@ -421,6 +425,8 @@
 .pi-circle-green {
   position: relative;
   padding-left: 14px;
+  line-height: 16px;
+  height: 16px;
   &::before {
     content: '';
     position: absolute;
@@ -438,6 +444,8 @@
 .pi-circle-red {
   position: relative;
   padding-left: 14px;
+  line-height: 16px;
+  height: 16px;
   &::before {
     content: '';
     position: absolute;
@@ -508,3 +516,22 @@
   background-color: #fff3cd;
   border-color: #ffeeba;
 }
+
+.pi-badge {
+  display: inline-block;
+  padding-right: .6em;
+  padding-left: .6em;
+  border-radius: 10rem;
+  font-size: 100%;
+  font-weight: 700;
+  padding: 0.25em 0.4em;
+  line-height: 1;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: baseline;
+  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+  &.danger {
+    color: #fff;
+    background-color: #dc3545;
+  }
+}

+ 111 - 98
src/components/AuditContent/index.tsx

@@ -59,7 +59,7 @@ interface iAuditContentProps {
 }
 
 const Index: React.FC<iAuditContentProps> = props => {
-  const { onSelect, auditors, onDelete, status, auditHistory, uName } = props
+  const { onSelect, auditors, onDelete, status, auditHistory } = props
   const [ visible, setVisible ] = useState({
     check: false,
     reCheck: false
@@ -147,35 +147,27 @@ const Index: React.FC<iAuditContentProps> = props => {
     )
   }
 
-  const renderStatusIcon = (status: number, isStart: boolean) => {
+  const renderStatusIcon = (status: number, isEnd: boolean) => {
     let bgColor: string = ''
     let iconType: string = ''
     switch (status) {
       case 0:
-        bgColor = 'pi-bg-gray'
-        iconType = ''
-        break
-      case 1:
-        bgColor = 'pi-bg-yellow'
-        iconType = 'xxh-ellipsis-h1'
-        break
-      case 2:
         bgColor = 'pi-bg-green'
         iconType = 'xxh-check'
         break
-      case 3:
+      case 1:
         bgColor = 'pi-bg-yellow'
         iconType = 'xxh-reply'
         break
-      case 4:
+      case 2:
         bgColor = 'pi-bg-red'
         iconType = 'xxh-minus'
         break
       default:
         break
     }
-    if (isStart) {
-      bgColor = 'pi-bg-green'
+
+    if (isEnd) {
       iconType = 'xxh-caret-down1'
     }
     return <div className={[ 'timeline-item-icon', 'pi-justify-center', 'pi-align-center', bgColor ].join(' ')}>{iconType ? <SvgIcon type={iconType}></SvgIcon> : null}</div>
@@ -186,56 +178,52 @@ const Index: React.FC<iAuditContentProps> = props => {
     let textClass = 'pi-green'
     if (progress === '0') {
       switch (status) {
-        case 1:
-          text = '审批中'
-          textClass = 'pi-yellow'
-          break
-        case 2:
-          text = '审批通过'
+        case 0:
+          text = '上报审批'
           textClass = 'pi-green'
           break
-        case 3:
+        case 1:
           text = '审批退回'
           textClass = 'pi-yellow'
           break
-        case 4:
-          text = '审批关闭'
+        case 2:
+          text = '关闭'
           textClass = 'pi-red'
           break
         default:
           break
       }
-    } else if (progress === '1') {
+    } else if (progress === '1' || progress === '3') {
       switch (status) {
-        case 1:
-          text = '整改中'
-          textClass = 'pi-yellow'
-          break
-        case 2:
-          text = '整改完成'
+        case 0:
+          text = '审批通过'
           textClass = 'pi-green'
           break
-        case 3:
+        case 1:
           text = '审批退回'
           textClass = 'pi-yellow'
           break
+        case 2:
+          text = '关闭'
+          textClass = 'pi-red'
+          break
         default:
           break
       }
     } else if (progress === '2') {
       switch (status) {
-        case 1:
-          text = '复查中'
-          textClass = 'pi-yellow'
-          break
-        case 2:
-          text = '复查完成'
+        case 0:
+          text = '整改完成'
           textClass = 'pi-green'
           break
-        case 3:
+        case 1:
           text = '审批退回'
           textClass = 'pi-yellow'
           break
+        case 2:
+          text = '关闭'
+          textClass = 'pi-red'
+          break
         default:
           break
       }
@@ -243,10 +231,31 @@ const Index: React.FC<iAuditContentProps> = props => {
     return { text, textClass }
   }
 
+  const renderLeftStatus = (status: number) => {
+    let text = ''
+    let textClass = ''
+    switch (status) {
+      case 1:
+        text = '进行中'
+        textClass = 'pi-yellow'
+        break
+      case 2:
+        text = '完成'
+        textClass = 'pi-green'
+        break
+      case 3:
+        text = '关闭'
+        textClass = 'pi-red'
+        break
+      default:
+        break
+    }
+    return { text, textClass }
+  }
+
   const renderLeftAuditors = (status: number) => {
     // 整改人所需信息
     const len = auditors.filter(item => item.progress === '0').length
-
     return (
       <tbody>
         <tr>
@@ -256,6 +265,11 @@ const Index: React.FC<iAuditContentProps> = props => {
             <span className="pi-mg-left-3">{auditors[0]?.name}</span>
             <small className="text-muted pi-mg-left-3">{auditors[0]?.position}</small>
           </td>
+          {status !== auditConsts.uncheck ? (
+            <td>
+              <span className={renderLeftStatus(auditors.length > 1 ? 2 : 0).textClass}>{renderLeftStatus(auditors.length > 1 ? 2 : 0).text}</span>
+            </td>
+          ) : null}
         </tr>
         {auditors
           .filter(item => item.progress === '0')
@@ -270,6 +284,11 @@ const Index: React.FC<iAuditContentProps> = props => {
                   <span className="pi-mg-left-3">{item.name}</span>
                   <small className="text-muted pi-mg-left-3">{item.position}</small>
                 </td>
+                {status !== auditConsts.uncheck ? (
+                  <td>
+                    <span className={renderLeftStatus(item.status).textClass}>{renderLeftStatus(item.status).text}</span>
+                  </td>
+                ) : null}
               </tr>
             ) : (
               <tr key={item.audit_id}>
@@ -278,6 +297,11 @@ const Index: React.FC<iAuditContentProps> = props => {
                   <span className="pi-mg-left-3">{item.name}</span>
                   <small className="text-muted pi-mg-left-3">{item.position}</small>
                 </td>
+                {status !== auditConsts.uncheck ? (
+                  <td>
+                    <span className={renderLeftStatus(item.status).textClass}>{renderLeftStatus(item.status).text}</span>
+                  </td>
+                ) : null}
               </tr>
             )
           })}
@@ -295,9 +319,22 @@ const Index: React.FC<iAuditContentProps> = props => {
                 <span className="pi-mg-left-3">由 {auditors.filter(item => item.progress === '0')[len - 1]?.name} 指派</span>
               )
             ) : (
-              <span className="pi-mg-left-3">由<span className="pi-mg-3">{auditors.filter(item => item.progress === '0').length ? auditors.filter(item => item.progress === '0')[len - 1]?.name : '最后一个审批人'}</span>指派</span>
+              <span className="pi-mg-left-3">
+                由
+                <span className="pi-mg-3">
+                  {auditors.filter(item => item.progress === '0').length ? auditors.filter(item => item.progress === '0')[len - 1]?.name : '最后一个审批人'}
+                </span>
+                指派
+              </span>
             )}
           </td>
+          {status !== auditConsts.uncheck ? (
+            <td>
+              <span className={renderLeftStatus(auditors.find(item => item.progress === '1')?.status || 0).textClass}>
+                {renderLeftStatus(auditors.find(item => item.progress === '1')?.status || 0).text}
+              </span>
+            </td>
+          ) : null}
         </tr>
         {auditors
           .filter(item => item.progress === '2')
@@ -312,6 +349,11 @@ const Index: React.FC<iAuditContentProps> = props => {
                   <span className="pi-mg-left-3">{item.name}</span>
                   <small className="text-muted pi-mg-left-3">{item.position}</small>
                 </td>
+                {status !== auditConsts.uncheck ? (
+                  <td>
+                    <span className={renderLeftStatus(item.status).textClass}>{renderLeftStatus(item.status).text}</span>
+                  </td>
+                ) : null}
               </tr>
             ) : (
               <tr key={item.audit_id}>
@@ -320,6 +362,11 @@ const Index: React.FC<iAuditContentProps> = props => {
                   <span className="pi-mg-left-3">{item.name}</span>
                   <small className="text-muted pi-mg-left-3">{item.position}</small>
                 </td>
+                {status !== auditConsts.uncheck ? (
+                  <td>
+                    <span className={renderLeftStatus(item.status).textClass}>{renderLeftStatus(item.status).text}</span>
+                  </td>
+                ) : null}
               </tr>
             )
           })}
@@ -330,73 +377,41 @@ const Index: React.FC<iAuditContentProps> = props => {
   const renderHistory = () => {
     return (
       <>
-      {auditHistory?.map((item, index) => {
-        return (
-          <ul className="timeline-list" key={index}>
-            {item?.map((auditor, idx) => (
-              <div key={idx}>
-                {index === 0 && idx === 0 ? (
+        {auditHistory?.map((item, index) => {
+          return (
+            <ul className="timeline-list" key={index}>
+              {item?.map((auditor, idx) => (
+                <div key={idx}>
                   <li className="timeline-list-item">
-                    <div className="timeline-item-date pi-flex-column" dangerouslySetInnerHTML={{ __html: formatDate(auditor.create_time) }}></div>
-                    <div className={item.length - 1 === idx ? '' : 'timeline-item-tail'}></div>
-                    {renderStatusIcon(auditor.status, true)}
-                    <div className="timeline-item-content">
-                      <div className="card-container">
-                        <div className="card-content">
-                          <div className="pi-justify-between label">
-                            <span>{uName}</span>
-                            <span className="pi-green">上报审批</span>
-                          </div>
-                        </div>
-                      </div>
-                    </div>
-                  </li>
-                ) : null}
-                <li className="timeline-list-item" >
-                  {auditor.status ? (
                     <div
                       className="timeline-item-date pi-flex-column"
-                      dangerouslySetInnerHTML={{ __html: formatDate(auditor.status === auditConsts.checked || auditor.status === auditConsts.close? auditor.end_time : auditor.create_time) }}></div>
-                  ) : null}
-                  <div className={item.length - 1 === idx ? '' : 'timeline-item-tail'}></div>
-                  {renderStatusIcon(auditor.status, false)}
-                  <div className="timeline-item-content">
-                    <div className="card-container">
-                      <div className="card-content">
-                        <div className="pi-justify-between label">
-                          <span>{auditor.name}</span>
-                          <span className={renderStatusEle(auditor.status, auditor.progress).textClass}>{renderStatusEle(auditor.status, auditor.progress).text}</span>
-                        </div>
-                        <div className="text-muted">{auditor.position}</div>
-                      </div>
-                      {auditor.opinion ? (
-                        <div className="textarea">
-                          <span>{auditor.opinion}</span>
-                        </div>
-                      ) : null}
-                    </div>
-                  </div>
-                </li>
-                {idx === item.filter(item => item.progress === '0').length - 1 && !auditors.find(item => item.progress === '1')? (
-                  <li className="timeline-list-item" >
-                    <div className="timeline-item-tail"></div>
-                    {renderStatusIcon(0, false)}
+                      dangerouslySetInnerHTML={{
+                        __html: formatDate(auditor.create_time)
+                      }}></div>
+                    <div className={item.length - 1 === idx ? '' : 'timeline-item-tail'}></div>
+                    {renderStatusIcon(auditor.status, idx === item.length - 1)}
                     <div className="timeline-item-content">
                       <div className="card-container">
                         <div className="card-content">
                           <div className="pi-justify-between label">
-                            <span>待指派</span>
+                            <span>{auditor.name}</span>
+                            <span className={renderStatusEle(auditor.status, auditor.progress).textClass}>{renderStatusEle(auditor.status, auditor.progress).text}</span>
                           </div>
+                          <div className="text-muted">{auditor.position}</div>
                         </div>
+                        {auditor.opinion ? (
+                          <div className="textarea">
+                            <span>{auditor.opinion}</span>
+                          </div>
+                        ) : null}
                       </div>
                     </div>
                   </li>
-                ) : null}
-              </div>
-            ))}
-          </ul>
-        )
-      })}
+                </div>
+              ))}
+            </ul>
+          )
+        })}
       </>
     )
   }
@@ -473,9 +488,7 @@ const Index: React.FC<iAuditContentProps> = props => {
                 <div className="pi-flex-sub pi-mg-right-15 pi-mg-top-5 pi-mg-left-5">
                   <table className="table table-bordered pi-width-100P">{renderLeftAuditors(status)}</table>
                 </div>
-                <div className="pi-flex-treble pi-mg-left-15">
-                  {renderHistory()}
-                </div>
+                <div className="pi-flex-twice pi-mg-left-15">{renderHistory()}</div>
               </div>
             </td>
           )}

+ 5 - 11
src/components/OssUpload/index.tsx

@@ -28,7 +28,9 @@ const UploadModal:React.FC<iUploadModalProps> = (props) => {
   })
   useEffect(() => {
     if (props.visible) {
+      // 初始化时要将上次上次的文件清除掉
       setFileList([])
+      setFiles([])
       initOssData()
     }
   }, [ props.visible ])
@@ -39,19 +41,11 @@ const UploadModal:React.FC<iUploadModalProps> = (props) => {
       // console.log(info.file, info.fileList)
     }
     if (status === 'done') {
-      // message.success(`${info.file.name} 上传成功!`)
       console.log(info)
-      const newFileList = [ ...fileList ]
-      newFileList.push(info.file)
+      const newFileList = [ ...fileList, ...info.fileList ]
+      // newFileList.push(info.file)
       setFileList(newFileList)
-      // const newFileList: iFile[] = info.fileList.map((item: UploadFile) => {
-      //   return {
-      //     createTime: new Date(),
-      //     filepath: item.url,
-      //     filename: item.name
-      //   }
-      // })
-      const newFiles = [ ...files, { createTime: new Date(), filepath: info.file.url,filename: info.file.name } ]
+      const newFiles = [ ...files, ...info.fileList.map(item => ({ createTime: new Date(), filepath: item.url,filename: item.name })) ]
       setFiles(newFiles)
     } else if (status === 'error') {
       message.error(`${info.file.name} 上传失败!`)

+ 1 - 1
src/components/SvgIcon/index.tsx

@@ -1,5 +1,5 @@
 import { createFromIconfontCN } from '@ant-design/icons'
 const IconFont = createFromIconfontCN({
-  scriptUrl: '//at.alicdn.com/t/font_2224180_1n7qk7cijqy.js'
+  scriptUrl: '//at.alicdn.com/t/font_2224180_1k48wjqufrd.js'
 })
 export default IconFont

+ 5 - 4
src/pages/Contract/Content/Income/api.ts

@@ -1,4 +1,5 @@
 import request from '@/utils/common/request'
+import consts from '@/utils/consts'
 
 /**
  * 标段升降、上下移、增删
@@ -27,7 +28,7 @@ export async function apiResfulContractTree(type: string, payload: object) {
     default:
       break
   }
-  const { data } = await request[method](url, payload)
+  const { data } = await request[method](url, { ...payload, treeType: consts.CONTRACT_TREE.RETURN })
   return data
 }
 
@@ -48,7 +49,7 @@ export async function apiGetIncome(id: string, bidsectionId: string) {
  * @param serial 序号
  */
 export async function apiUpdateSerial(id: string, bidsectionId: string, serial: string) {
-  const { data } = await request.post('/api/contract/section/serial/update', { id, bidsectionId, serial: parseInt(serial) })
+  const { data } = await request.post('/api/contract/section/serial/update', { id, bidsectionId, serial: parseInt(serial), treeType: consts.CONTRACT_TREE.RETURN })
   return data
 }
 
@@ -59,7 +60,7 @@ export async function apiUpdateSerial(id: string, bidsectionId: string, serial:
  * @param name 名称
  */
 export async function apiUpdateName(id: string, bidsectionId: string, name: string) {
-  const { data } = await request.post('/api/contract/section/save', { id, bidsectionId, name })
+  const { data } = await request.post('/api/contract/section/save', { id, bidsectionId, name, treeType: consts.CONTRACT_TREE.RETURN })
   return data
 }
 
@@ -98,6 +99,6 @@ export async function apiResfulContract(type: string, payload: object) {
     default:
       break
   }
-  const { data } = await request[method](url, payload)
+  const { data } = await request[method](url, { ...payload, treeType: consts.CONTRACT_TREE.RETURN })
   return data
 }

+ 12 - 2
src/pages/Contract/Content/Income/components/Modal/api.ts

@@ -1,11 +1,21 @@
 import request from '@/utils/common/request'
 
+/**
+ * 获得标段回款-项目节信息
+ * @param bidsectionId 标段id
+ */
 export async function apiContractIncome(bidsectionId: string) {
   const { data } = await request.get('/api/contract/income/section/all', { bidsectionId })
   return data
 }
 
-export async function apiSetTemplate(templateNumber: string, bidsectionId: string) {
-  const { data } = await request.post('/api/contract/section/template', { templateNumber: parseInt(templateNumber), bidsectionId })
+/**
+ *
+ * @param templateNumber 模板号
+ * @param bidsectionId 标段id
+ * @param treeType 项目节类型(0收入1支出)默认为0
+ */
+export async function apiSetTemplate(templateNumber: string, bidsectionId: string, treeType: number) {
+  const { data } = await request.post('/api/contract/section/template', { templateNumber: parseInt(templateNumber), bidsectionId, treeType: treeType })
   return data
 }

+ 9 - 8
src/pages/Contract/Content/Income/components/Modal/index.tsx

@@ -1,11 +1,12 @@
 import DatePicker from '@/components/DatePicker'
 import { apiAutoCode } from '@/pages/Safe/Content/List/api'
-import { contractStore, tenderStore } from '@/store/mobx'
+import { contractReturnStore, tenderStore } from '@/store/mobx'
 import { iModalCommonProps } from '@/types/contract'
 import consts from '@/utils/consts'
 import { dayjsFormat } from '@/utils/util'
 import { Button, Form, Input, Modal, Select } from 'antd'
 import locale from 'antd/es/date-picker/locale/zh_CN'
+import dayjs from 'dayjs'
 import React, { useEffect, useState } from 'react'
 import { apiGetReturnWay } from '../Tabs/Receivable/api'
 import styles from './index.module.scss'
@@ -51,8 +52,8 @@ const ContractModal: React.FC<iModalCommonProps> = ({ modalObj: { type, visible,
     if (visible) {
       form.setFieldsValue({ treeId: row.id, bidsectionId: row.bidsectionId })
       if (type === "update") {
-        const { content="", name="", price="", partyA="", partyB="", partyASigner="",partyBSigner="" } = contractStore.contract
-        form.setFieldsValue({ content, name, price, partyA, partyB, partyASigner, partyBSigner })
+        const { content="", name="", price="", partyA="", partyB="", partyASigner="",partyBSigner="", signerTime = '', remarks = '' } = contractReturnStore.contract
+        form.setFieldsValue({ content, name, price, partyA, partyB, partyASigner, partyBSigner, signerTime: dayjs(signerTime), remarks })
       } else if (type === 'return') {
         apiGetReturnWay().then(({ code = -1, data = [] }) => {
           if (code === consts.RET_CODE.SUCCESS) {
@@ -60,14 +61,14 @@ const ContractModal: React.FC<iModalCommonProps> = ({ modalObj: { type, visible,
             setOptions(options)
           }
         })
-        form.setFieldsValue({ contractsId: contractStore.contract.id })
+        form.setFieldsValue({ contractsId: contractReturnStore.contract.id })
       } else {
-        form.setFieldsValue({ id: contractStore.contract.id })
+        form.setFieldsValue({ id: contractReturnStore.contract.id })
       }
     }
   }, [ visible ])
   const autoCodeHandler = async () => {
-    const { code = -1, data = "" } = await apiAutoCode(tenderStore.tender.bidsectionId, 'contractRule')
+    const { code = -1, data = "" } = await apiAutoCode(tenderStore.tender.bidsectionId, 'contractReturnRule')
     if (code === consts.RET_CODE.SUCCESS) {
       if (data) {
         const ruleArr: string[] = []
@@ -88,7 +89,7 @@ const ContractModal: React.FC<iModalCommonProps> = ({ modalObj: { type, visible,
     <Modal
       getContainer={false}
       visible={visible}
-      title={modalObj[type].title}
+      title={modalObj[type]?.title}
       onCancel={onCancel}
       footer={<div className="pi-justify-end">
         {type === 'update' ? <Button type="primary" key="delete" size="small" danger onClick={() => reload('del')}>删除合同</Button> : ''}
@@ -145,7 +146,7 @@ const ContractModal: React.FC<iModalCommonProps> = ({ modalObj: { type, visible,
               </Select>
             </Form.Item> */}
             <Form.Item name="price" label="合同金额" rules={[ { required: true, message: '请输入合同金额' } ]}>
-              <Input placeholder="输入合同金额" addonAfter={<span>元</span>}></Input>
+              <Input type="number" placeholder="输入合同金额" addonAfter={<span>元</span>}></Input>
             </Form.Item>
           </>
           ) : ''

+ 18 - 33
src/pages/Contract/Content/Income/components/TableContent/index.tsx

@@ -1,7 +1,7 @@
 import { ZhSubmitButton } from '@/components/Button'
 import OssUploadModal from '@/components/OssUpload'
-import { contractStore, tenderStore } from '@/store/mobx'
-import { iIncomeTree } from '@/types/contract'
+import { contractReturnStore, tenderStore } from '@/store/mobx'
+import { iIncomeTree, iShowTemplateState, iTemplateState } from '@/types/contract'
 import { iFile } from '@/types/file'
 import { apiSaveFileInfo } from '@/utils/common/api'
 import { contractConsts } from '@/utils/common/constStatus'
@@ -23,22 +23,7 @@ interface iTableContentPorps {
   row: iIncomeTree
   setRow: (record: iIncomeTree) => void
 }
-interface iTemplateState {
-  attribution: string
-  children: iTemplateState[] | undefined
-  depth: number
-  id: number
-  isEnd: boolean
-  leaf: boolean
-  name: string
-  parentId: number
-  serial: string
-}
-interface iShowTemplateState {
-  isShow: boolean
-  template: string
-  loading: boolean
-}
+
 
 const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) => {
   const [ sectionTemplate, setSectionTemplate ] = useState<iShowTemplateState>({
@@ -93,7 +78,7 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
           template2: data.sectionTemplate2
         })
       } else {
-        contractStore.updateTree(data.sectionTree.children)
+        contractReturnStore.updateTree(data.sectionTree.children)
       }
     }
   }
@@ -116,7 +101,7 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
       RET_CODE = code
     }
     if (RET_CODE === consts.RET_CODE.SUCCESS) {
-      contractStore.resetTree(tenderStore.tender.bidsectionId)
+      contractReturnStore.resetTree(tenderStore.tender.bidsectionId)
     }
   }
 
@@ -213,7 +198,7 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
       title: '状态',
       dataIndex: 'contractStatus',
       // eslint-disable-next-line react/display-name
-      render: (_: any, record: iIncomeTree) => record.contractCode ? <span><i className={contractConsts[record.contractStatus].className}></i>{contractConsts[record.contractStatus].text}</span> : ''
+      render: (_: any, record: iIncomeTree) => record.contractCode ? <span className={contractConsts[record.contractStatus].className}>{contractConsts[record.contractStatus].text}</span> : ''
     }
 
   ]
@@ -231,7 +216,7 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
       })
       return message.error('请选择项目节模板!')
     }
-    const { code = -1 } = await apiSetTemplate(sectionTemplate.template, tenderStore.tender.bidsectionId)
+    const { code = -1 } = await apiSetTemplate(sectionTemplate.template, tenderStore.tender.bidsectionId, 0)
     if (code === consts.RET_CODE.SUCCESS) {
       await initHandler()
     }
@@ -257,7 +242,7 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
         rowClickHandler(record.id, record.bidsectionId, record.isEdit, record.isNew)
       },
       onDoubleClick() {
-        contractStore.rowChange(row.id)
+        contractReturnStore.rowChange(row.id)
       }
     }
   }
@@ -268,7 +253,7 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
       const { code = -1, section = {}, contract: newContract = {} } = await apiGetIncome(id, bid)
       if (code === consts.RET_CODE.SUCCESS) {
         setRow(section)
-        contractStore.updateContract(newContract)
+        contractReturnStore.updateContract(newContract)
       }
     }
   }
@@ -276,16 +261,16 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
     return record.id === row.id ? 'ant-table-row-selected' : ''
   }
   const tabOnClick = (key: string) => {
-    contractStore.changeUpdate(key)
+    contractReturnStore.changeUpdate(key)
   }
 
   // 阿里oss上传弹窗
   const onShow = (show: boolean) => setVisible(show)
   const onCreate = async (fileList: iFile[]) => {
-    const { code = -1 } = await apiSaveFileInfo(fileList, consts.DATA_TYPE.CONTRACT, row.contractId)
+    const { code = -1 } = await apiSaveFileInfo(fileList, consts.DATA_TYPE.CONTRACT_RETURN, row.contractId)
     if (code === consts.RET_CODE.SUCCESS) {
       setVisible(false)
-      contractStore.changeUpdate('3')
+      contractReturnStore.changeUpdate('3')
     }
   }
 
@@ -352,9 +337,9 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
   <div className={styles.spreadContent}>
     <div className={styles.spreadSheets}>
       {
-        contractStore.showTable ?
+        contractReturnStore.showTable ?
           <Table<iIncomeTree>
-            dataSource={contractStore.tree}
+            dataSource={contractReturnStore.tree}
             columns={TableColumns}
             bordered
             pagination={false}
@@ -376,7 +361,7 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
         tabBarExtraContent={{ right:
           <div className="pi-mg-right-5 pi-flex-row">
             {
-              contractStore.contract.id && contractStore.contract.status === contractConsts.status.checking ?
+              contractReturnStore.contract.id && contractReturnStore.contract.status === contractConsts.status.checking ?
               <>
                 <Button type="primary" size="small" onClick={() => modalHandler('update')} className="pi-mg-right-5">编辑合同</Button>
                 <Button type="primary" size="small" onClick={() => modalHandler('return')} className="pi-mg-right-5">添加回款</Button>
@@ -385,19 +370,19 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
               : ''
             }
             {
-              contractStore.contract.id && contractStore.contract.status === contractConsts.status.willClose ?
+              contractReturnStore.contract.id && contractReturnStore.contract.status === contractConsts.status.willClose ?
               <Button type="primary" size="small" danger className="pi-mg-right-3" onClick={() => modalHandler('close')}>关闭合同</Button>
               : ''
             }
             {
-              contractStore.contract.id && contractStore.contract.status === contractConsts.status.closed ?
+              contractReturnStore.contract.id && contractReturnStore.contract.status === contractConsts.status.closed ?
               <ZhSubmitButton type="primary" size="small" danger className="pi-mg-right-3" onClick={() => modalHandler('unlock')}>解锁合同</ZhSubmitButton>
               : ''
             }
           </div>
         }}>
         <TabPane key="1" tab="合同详情">
-          <Detail {...contractStore.contract}></Detail>
+          <Detail {...contractReturnStore.contract}></Detail>
         </TabPane>
         <TabPane key="2" tab="合同回款">
           <Receivable></Receivable>

+ 20 - 9
src/pages/Contract/Content/Income/components/Tabs/Detail/index.tsx

@@ -1,27 +1,38 @@
 import { iContractState } from '@/types/contract'
 import { contractConsts } from '@/utils/common/constStatus'
-import { dayjsFormat } from '@/utils/util'
-import React from 'react'
+import { dayjsFormat, formatMoney } from '@/utils/util'
+import { Tooltip } from 'antd'
+import React, { useMemo } from 'react'
 import styles from './index.module.scss'
 export default function Detail(props: iContractState) {
-  return (
+  const progress = useMemo(() => {
+    const i = parseFloat((parseFloat(props.returned) / parseFloat(props.price)).toFixed(1))
+    const j: number = 1 - i
+    const k: number = parseFloat(props.price) - parseFloat(props.returned)
+    return { returned: isNaN(i) ? '0%' : i * 100 + '%', unReturned: isNaN(j) ? '100%' : j * 100 + '%', unReturnedMoney: k }
+  }, [ props.id ])
+  return props.id ? (
     <div className={styles.detailTab}>
       <table className={styles.detailTable}>
         <tbody>
         <tr><th style={{ width: '10%' }}>项目内容</th><td style={{ width: '40%' }}>{props.content}</td><th style={{ width: '10%' }}>合同名称</th><td style={{ width: '40%' }}>{props.name}</td></tr>
-        <tr><th>合同编号</th><td>{props.code}</td><th>状态</th><td><i className={contractConsts[props.status].class}></i>{contractConsts[props.status].text}</td></tr>
+        <tr><th>合同编号</th><td>{props.code}</td><th>状态</th><td><span className={contractConsts[props.status].className}>{contractConsts[props.status].text}</span></td></tr>
         <tr><th>合同金额</th><td>{props.price}</td><th>创建时间</th><td>{props.createTime}</td></tr>
-        <tr><th>回款金额</th><td>{props.paid}</td><th>未回款金额</th><td>2,769,700.00</td></tr>
+        <tr><th>回款金额</th><td>{formatMoney(parseFloat(props.returned))}</td><th>未回款金额</th><td>{formatMoney(progress.unReturnedMoney)}</td></tr>
           <tr><th>回款进度</th><td className={styles.progressContainer} colSpan={3}><div className={styles.progressContent}>
-            <div className={[ styles.progressBar, 'pi-bg-success' ].join(' ')} style={{ width: '57.7%' }} >57.7%</div>
-            <div className={[ styles.progressBar, 'pi-bg-gray' ].join(' ')} style={{ width: '42.3%' }}>42.3%</div>
+            <Tooltip title={`已支付:¥ ${formatMoney(parseFloat(props.returned))}`}>
+              <div className={[ styles.progressBar, 'pi-bg-success' ].join(' ')} style={{ width: progress.returned }}>{progress.returned}</div>
+            </Tooltip>
+            <Tooltip title={`未支付:¥ ${formatMoney(progress.unReturnedMoney)}`}>
+              <div className={[ styles.progressBar, 'pi-bg-gray' ].join(' ')} style={{ width: progress.unReturned }}>{progress.unReturned}</div>
+            </Tooltip>
           </div></td></tr>
         <tr><th>甲方</th><td>{props.partyA}</td><th>甲方签约人</th><td>{props.partyASigner}</td></tr>
         <tr><th>乙方</th><td>{props.partyB}</td><th>乙方签约人</th><td>{props.partyBSigner}</td></tr>
-        <tr><th>签约日期</th><td colSpan={3}>{props.signerTime && dayjsFormat(props.signerTime, 'YYYY-MM-DD')}</td></tr>
+        <tr><th>签约日期</th><td colSpan={3}>{dayjsFormat(props.signerTime, 'YYYY-MM-DD')}</td></tr>
         <tr><th>备注</th><td colSpan={3}>{props.remarks}</td></tr>
         </tbody>
       </table>
     </div>
-  )
+  ) : null
 }

+ 8 - 8
src/pages/Contract/Content/Income/components/Tabs/File/index.tsx

@@ -1,4 +1,4 @@
-import { contractStore } from '@/store/mobx'
+import { contractReturnStore } from '@/store/mobx'
 import { apiDelFile, apiGetFileList } from '@/utils/common/api'
 import consts from '@/utils/consts'
 import { dayjsFormat } from '@/utils/util'
@@ -19,18 +19,18 @@ const File:React.FC<{}> = () => {
   const [ total, setTotal ] = useState<number>(0)
   const [ id, setId ] = useState<string>('')
   useEffect(() => {
-    if (contractStore.contract.id) {
-      if (contractStore.contract.id !== id) {
-        setId(contractStore.contract.id)
+    if (contractReturnStore.contract.id) {
+      if (contractReturnStore.contract.id !== id) {
+        setId(contractReturnStore.contract.id)
         initData()
-      } else if (contractStore.shouldUpdate && contractStore.shouldUpdate === '3') {
+      } else if (contractReturnStore.shouldUpdate && contractReturnStore.shouldUpdate === '3') {
         initData()
       }
-      contractStore.shouldUpdate && (contractStore.changeUpdate(''))
+      contractReturnStore.shouldUpdate && (contractReturnStore.changeUpdate(''))
     }
-  }, [ contractStore.contract.id, contractStore.shouldUpdate ])
+  }, [ contractReturnStore.contract.id, contractReturnStore.shouldUpdate ])
   const initData = async(pageNo: number = 1, pageSize: number = 7) => {
-    const { code = -1, data = [], total = 0 } = await apiGetFileList(consts.DATA_TYPE.CONTRACT, contractStore.contract.id, pageNo, pageSize)
+    const { code = -1, data = [], total = 0 } = await apiGetFileList(consts.DATA_TYPE.CONTRACT_RETURN, contractReturnStore.contract.id, pageNo, pageSize)
     if (code === consts.RET_CODE.SUCCESS) {
       setData(data)
       setTotal(total)

+ 10 - 34
src/pages/Contract/Content/Income/components/Tabs/Receivable/index.tsx

@@ -1,6 +1,7 @@
 import DatePicker from '@/components/DatePicker'
 import FileModal from '@/components/FileModal'
-import { contractStore } from '@/store/mobx'
+import { contractReturnStore } from '@/store/mobx'
+import { iReceivableState, iEditableCellProps } from '@/types/contract'
 import { iFileModal } from '@/types/file'
 import consts from '@/utils/consts'
 import { dayjsFormat } from '@/utils/util'
@@ -12,33 +13,8 @@ import { observer } from 'mobx-react'
 import React, { useEffect, useMemo, useState } from 'react'
 import { apiDelReturn, apiGetReturns, apiUpdateReturn } from './api'
 
-interface iReceivableState {
-  accountId: string;
-  annexes: number;
-  bidsectionId: string;
-  contractsId: string;
-  createTime: string;
-  createUser: string;
-  fileCounts: number;
-  id: string;
-  page: number;
-  price: string;
-  projectId: string;
-  remarks: string;
-  time: string;
-  way: string;
-}
-interface EditableCellProps extends React.HTMLAttributes<HTMLElement> {
-  editing: boolean;
-  dataIndex: string;
-  title: any;
-  cellType: 'DatePicker' | 'text';
-  record: iReceivableState;
-  index: number;
-  children: React.ReactNode;
-}
 
-const EditableCell: React.FC<EditableCellProps> = ({
+const EditableCell: React.FC<iEditableCellProps> = ({
   editing,
   dataIndex,
   title,
@@ -89,18 +65,18 @@ const Receivable:React.FC<{}> = () => {
   }
 
   useEffect(() => {
-    if (contractStore.contract.id) {
-      if (contractStore.contract.id !== id) {
-        setId(contractStore.contract.id)
+    if (contractReturnStore.contract.id) {
+      if (contractReturnStore.contract.id !== id) {
+        setId(contractReturnStore.contract.id)
         initData()
-      } else if (contractStore.shouldUpdate && contractStore.shouldUpdate === '2') {
+      } else if (contractReturnStore.shouldUpdate && contractReturnStore.shouldUpdate === '2') {
         initData()
       }
-      contractStore.shouldUpdate && (contractStore.changeUpdate(''))
+      contractReturnStore.shouldUpdate && (contractReturnStore.changeUpdate(''))
     }
-  }, [ contractStore.contract.id, contractStore.shouldUpdate ])
+  }, [ contractReturnStore.contract.id, contractReturnStore.shouldUpdate ])
   const initData = async() => {
-    const { code = -1, data = [] } = await apiGetReturns(contractStore.contract.id, contractStore.contract.bidsectionId)
+    const { code = -1, data = [] } = await apiGetReturns(contractReturnStore.contract.id, contractReturnStore.contract.bidsectionId)
       if (code === consts.RET_CODE.SUCCESS) {
         setData(data)
       }

+ 11 - 12
src/pages/Contract/Content/Income/index.tsx

@@ -3,11 +3,10 @@ import Slot from '@/components/Header/slot'
 import RuleModal from '@/components/RuleModal'
 import SvgIcon from '@/components/SvgIcon'
 import { apiSaveRule } from '@/pages/Safe/Content/List/api'
-import { contractStore, tenderStore } from '@/store/mobx'
+import { contractReturnStore, tenderStore } from '@/store/mobx'
 import { iIncomeTree, iModalBooleanProps } from '@/types/contract'
 import { contractTreeBaseId } from '@/utils/common/constStatus'
 import consts from '@/utils/consts'
-import { SettingOutlined } from '@ant-design/icons'
 import { Button, message, Tooltip } from 'antd'
 import React, { useMemo, useState } from 'react'
 import { apiResfulContract, apiResfulContractTree } from './api'
@@ -21,7 +20,7 @@ interface iModal {
 
 export default function Income() {
   const [ modalObj, setModalObj ] = useState<iModalBooleanProps>({
-    type: 'create',
+    type: '',
     visible: false,
     confirmLoading: false
   })
@@ -59,12 +58,12 @@ export default function Income() {
       ...modalObj,
       confirmLoading: true
     })
-    const { code = -1 } = await apiResfulContract(type, values)
+    const { code = -1, contract = {} } = await apiResfulContract(type, values)
     if (code === consts.RET_CODE.SUCCESS) {
-      // contractStore.updateContract(section)
-      contractStore.resetTree(tenderStore.bid)
+      contractReturnStore.updateContract(contract)
+      contractReturnStore.resetTree(tenderStore.bid)
       if (type === 'return') {
-        contractStore.changeUpdate('2')
+        contractReturnStore.changeUpdate('2')
       }
     }
     setModalObj({
@@ -83,11 +82,11 @@ export default function Income() {
   }
   const treeResfulApiHandler = async (type: string, payload: any) => {
     if (type === 'add') {
-      return contractStore.addRowTree(payload.id)
+      return contractReturnStore.addRowTree(payload.id)
     }
     const { code = -1, section = {} } = await apiResfulContractTree(type, payload)
     if ( code === consts.RET_CODE.SUCCESS) {
-      contractStore.resetTree(tenderStore.bid)
+      contractReturnStore.resetTree(tenderStore.bid)
       if (type !== 'add' && type !== 'del') {
         setRow({ ...row, ...section })
       }
@@ -96,7 +95,7 @@ export default function Income() {
 
   const onRuleCreate = async (ruleValue: any) => {
     setRuleModal({ ...ruleModal, loading: true })
-    const { code = -1 } = await apiSaveRule({ bidsectionId: tenderStore.bid, type: 'contract_rule', rule: ruleValue })
+    const { code = -1 } = await apiSaveRule({ bidsectionId: tenderStore.bid, type: 'contract_return_rule', rule: ruleValue })
     if (code === consts.RET_CODE.SUCCESS) {
       message.success("规则更改成功!")
     }
@@ -170,7 +169,7 @@ export default function Income() {
           </div>
         </Slot>
         <Slot position="right">
-          <Button type="ghost" size="small" icon={<SettingOutlined />} className="pi-mg-right-3" style={{ color: '#007bff' }} onClick={() => setRuleModal({ ...ruleModal, visible: true })}>设置</Button>
+          <Button type="ghost" size="small" icon={<SvgIcon type="xxh-cog" />} className="pi-mg-right-3" style={{ color: '#007bff' }} onClick={() => setRuleModal({ ...ruleModal, visible: true })}>设置</Button>
           {
             showCBtn ? <Button type="primary" size="small" onClick={() => setModalObj({ ...modalObj, type: 'create', visible: true })}>新建收入合同</Button>
             : ""
@@ -179,7 +178,7 @@ export default function Income() {
       </Header>
       <TableContent modalHandler={modalHandler} row={row} setRow={(record: iIncomeTree) => setRow({ ...row, ...record })}></TableContent>
       <RuleModal
-        type={consts.RULE.CONTRACT}
+        type={consts.RULE.CONTRACT_RETURN}
         title="合同管理编号设置"
         visible={ruleModal.visible}
         onCreate={onRuleCreate}

+ 12 - 11
src/pages/Contract/Content/Spending/api.ts

@@ -1,4 +1,5 @@
 import request from '@/utils/common/request'
+import consts from '@/utils/consts'
 
 /**
  * 标段升降、上下移、增删
@@ -27,7 +28,7 @@ export async function apiResfulContractTree(type: string, payload: object) {
     default:
       break
   }
-  const { data } = await request[method](url, payload)
+  const { data } = await request[method](url, { ...payload, treeType: consts.CONTRACT_TREE.PAID })
   return data
 }
 
@@ -36,8 +37,8 @@ export async function apiResfulContractTree(type: string, payload: object) {
  * @param id - 项目节id
  * @param bidsectionId - 标段id
  */
-export async function apiGetIncome(id: string, bidsectionId: string) {
-  const { data } = await request.get('/api/contract/income', { id, bidsectionId })
+export async function apiGetExpenditure(id: string, bidsectionId: string) {
+  const { data } = await request.get('/api/contract/expenditure', { id, bidsectionId })
   return data
 }
 
@@ -48,7 +49,7 @@ export async function apiGetIncome(id: string, bidsectionId: string) {
  * @param serial 序号
  */
 export async function apiUpdateSerial(id: string, bidsectionId: string, serial: string) {
-  const { data } = await request.post('/api/contract/section/serial/update', { id, bidsectionId, serial: parseInt(serial) })
+  const { data } = await request.post('/api/contract/section/serial/update', { id, bidsectionId, serial: parseInt(serial), treeType: consts.CONTRACT_TREE.PAID })
   return data
 }
 
@@ -59,12 +60,12 @@ export async function apiUpdateSerial(id: string, bidsectionId: string, serial:
  * @param name 名称
  */
 export async function apiUpdateName(id: string, bidsectionId: string, name: string) {
-  const { data } = await request.post('/api/contract/section/save', { id, bidsectionId, name })
+  const { data } = await request.post('/api/contract/section/save', { id, bidsectionId, name, treeType: consts.CONTRACT_TREE.PAID })
   return data
 }
 
 /**
- * 合同增删改(包括添加回款)
+ * 合同增删改(包括添加已支付)
  * @param type - 操作类型
  * @param payload - 载荷
  */
@@ -72,11 +73,11 @@ export async function apiResfulContract(type: string, payload: object) {
   let url: string = '', method: string = ''
   switch (type) {
     case 'create':
-      url = '/api/contract/income/create'
+      url = '/api/contract/paid/create'
       method = 'post'
       break
     case 'update':
-      url = '/api/contract/income/update'
+      url = '/api/contract/paid/update'
       method = 'post'
       break
     case 'close':
@@ -84,15 +85,15 @@ export async function apiResfulContract(type: string, payload: object) {
       method = 'post'
       break
     case 'del':
-      url = '/api/contract'
+      url = '/api/contract/expenditure'
       method = 'del'
       break
     case 'unlock':
       url = '/api/contract/unlock'
       method = 'post'
       break
-    case 'return':
-      url = '/api/contract/return/create'
+    case 'paid':
+      url = '/api/contract/paid/create'
       method = 'post'
       break
     default:

+ 6 - 6
src/pages/Contract/Content/Spending/components/Modal/api.ts

@@ -1,11 +1,11 @@
 import request from '@/utils/common/request'
 
-export async function apiContractIncome(bidsectionId: string) {
-  const { data } = await request.get('/api/contract/income/section/all', { bidsectionId })
+/**
+ * 获得标段支出-项目节信息
+ * @param bidsectionId 标段id
+ */
+export async function apiContractExpenditure(bidsectionId: string) {
+  const { data } = await request.get('/api/contract/expenditure/section/all', { bidsectionId })
   return data
 }
 
-export async function apiSetTemplate(templateNumber: string, bidsectionId: string) {
-  const { data } = await request.post('/api/contract/section/template', { templateNumber: parseInt(templateNumber), bidsectionId })
-  return data
-}

+ 19 - 18
src/pages/Contract/Content/Spending/components/Modal/index.tsx

@@ -1,13 +1,14 @@
 import DatePicker from '@/components/DatePicker'
 import { apiAutoCode } from '@/pages/Safe/Content/List/api'
-import { contractStore, tenderStore } from '@/store/mobx'
+import { contractPaidStore, tenderStore } from '@/store/mobx'
 import { iModalCommonProps } from '@/types/contract'
 import consts from '@/utils/consts'
 import { dayjsFormat } from '@/utils/util'
 import { Button, Form, Input, Modal, Select } from 'antd'
 import locale from 'antd/es/date-picker/locale/zh_CN'
+import dayjs from 'dayjs'
 import React, { useEffect, useState } from 'react'
-import { apiGetReturnWay } from '../Tabs/Receivable/api'
+import { apiGetPaidWay } from '../Tabs/Receivable/api'
 import styles from './index.module.scss'
 const ContractModal: React.FC<iModalCommonProps> = ({ modalObj: { type, visible, confirmLoading }, onConfirm, onCancel, reload, row }) => {
   const { Option } = Select
@@ -39,8 +40,8 @@ const ContractModal: React.FC<iModalCommonProps> = ({ modalObj: { type, visible,
       cancelText: '取消',
       okText: '确认解锁'
     },
-    return: {
-      title: '添加回款',
+    paid: {
+      title: '添加支出',
       cancelText: '关闭',
       okText: '确认'
     }
@@ -51,23 +52,23 @@ const ContractModal: React.FC<iModalCommonProps> = ({ modalObj: { type, visible,
     if (visible) {
       form.setFieldsValue({ treeId: row.id, bidsectionId: row.bidsectionId })
       if (type === "update") {
-        const { content="", name="", price="", partyA="", partyB="", partyASigner="",partyBSigner="" } = contractStore.contract
-        form.setFieldsValue({ content, name, price, partyA, partyB, partyASigner, partyBSigner })
-      } else if (type === 'return') {
-        apiGetReturnWay().then(({ code = -1, data = [] }) => {
+        const { content="", name="", price="", partyA="", partyB="", partyASigner="",partyBSigner="", signerTime = '', remarks = '' } = contractPaidStore.contract
+        form.setFieldsValue({ content, name, price, partyA, partyB, partyASigner, partyBSigner, signerTime: dayjs(signerTime), remarks })
+      } else if (type === 'paid') {
+        apiGetPaidWay().then(({ code = -1, data = [] }) => {
           if (code === consts.RET_CODE.SUCCESS) {
             const options = data.map((item: string) => <Option key={item} value={item}>{item}</Option>)
             setOptions(options)
           }
         })
-        form.setFieldsValue({ contractsId: contractStore.contract.id })
+        form.setFieldsValue({ contractsId: contractPaidStore.contract.id })
       } else {
-        form.setFieldsValue({ id: contractStore.contract.id })
+        form.setFieldsValue({ id: contractPaidStore.contract.id })
       }
     }
   }, [ visible ])
   const autoCodeHandler = async () => {
-    const { code = -1, data = "" } = await apiAutoCode(tenderStore.tender.bidsectionId, 'contractRule')
+    const { code = -1, data = "" } = await apiAutoCode(tenderStore.tender.bidsectionId, 'contractPaidRule')
     if (code === consts.RET_CODE.SUCCESS) {
       if (data) {
         const ruleArr: string[] = []
@@ -108,7 +109,7 @@ const ContractModal: React.FC<iModalCommonProps> = ({ modalObj: { type, visible,
               if (type === 'del') {
                 delete values.warningText
               }
-              if (type === 'return') {
+              if (type === 'paid') {
                 values.time = dayjsFormat(values.time, 'YYYY-MM-DD HH:mm:ss')
               }
               onConfirm(values, type)
@@ -138,12 +139,12 @@ const ContractModal: React.FC<iModalCommonProps> = ({ modalObj: { type, visible,
             <Form.Item name="name" label="合同名称" rules={[ { required: true, message: '请输入合同名称' } ]}>
               <Input placeholder="输入合同名称"></Input>
             </Form.Item>
-            <Form.Item name="contractsType" label="合同类型" rules={[ { required: true, message: '请选择合同类型' } ]}>
+            {/* <Form.Item name="contractsType" label="合同类型" rules={[ { required: true, message: '请选择合同类型' } ]}>
               <Select showSearch>
                 <Option value={1}>支出合同</Option>
                 <Option value={2}>收入合同</Option>
               </Select>
-            </Form.Item>
+            </Form.Item> */}
             <Form.Item name="price" label="合同金额" rules={[ { required: true, message: '请输入合同金额' } ]}>
               <Input placeholder="输入合同金额" addonAfter={<span>元</span>}></Input>
             </Form.Item>
@@ -219,16 +220,16 @@ const ContractModal: React.FC<iModalCommonProps> = ({ modalObj: { type, visible,
           ) : ''
         }
         {
-          type === 'return' ?
+          type === 'paid' ?
           <>
             <Form.Item name="contractsId" hidden><Input></Input></Form.Item>
-            <Form.Item name="time" label="回款日期" rules={[ { required: true, message: '请选择回款日期' } ]}>
+            <Form.Item name="time" label="支出日期" rules={[ { required: true, message: '请选择支出日期' } ]}>
               <DatePicker allowClear locale={locale} className="pi-width-100P"></DatePicker>
             </Form.Item>
-            <Form.Item name="price" label="回款金额" rules={[ { required: true, message: '请选择回款金额' } ]}>
+            <Form.Item name="price" label="支出金额" rules={[ { required: true, message: '请选择支出金额' } ]}>
               <Input></Input>
             </Form.Item>
-            <Form.Item name="way" label="支付方式" rules={[ { required: true, message: '请选择回款方式' } ]}>
+            <Form.Item name="way" label="支付方式" rules={[ { required: true, message: '请选择支出方式' } ]}>
               <Select>
                 {options}
               </Select>

+ 27 - 41
src/pages/Contract/Content/Spending/components/TableContent/index.tsx

@@ -1,7 +1,7 @@
 import { ZhSubmitButton } from '@/components/Button'
 import OssUploadModal from '@/components/OssUpload'
-import { contractStore, tenderStore } from '@/store/mobx'
-import { iIncomeTree } from '@/types/contract'
+import { contractPaidStore, tenderStore } from '@/store/mobx'
+import { iIncomeTree, iShowTemplateState, iTemplateState } from '@/types/contract'
 import { iFile } from '@/types/file'
 import { apiSaveFileInfo } from '@/utils/common/api'
 import { contractConsts } from '@/utils/common/constStatus'
@@ -12,8 +12,9 @@ import { RadioChangeEvent } from 'antd/lib/radio'
 import { ColumnsType } from 'antd/lib/table'
 import { observer } from 'mobx-react'
 import React, { KeyboardEvent, useEffect, useRef, useState } from 'react'
-import { apiGetIncome, apiResfulContractTree, apiUpdateName, apiUpdateSerial } from '../../api'
-import { apiContractIncome, apiSetTemplate } from '../Modal/api'
+import { apiSetTemplate } from '../../../Income/components/Modal/api'
+import { apiGetExpenditure, apiResfulContractTree, apiUpdateName, apiUpdateSerial } from '../../api'
+import { apiContractExpenditure } from '../Modal/api'
 import Detail from '../Tabs/Detail'
 import File from '../Tabs/File'
 import Receivable from '../Tabs/Receivable'
@@ -23,22 +24,7 @@ interface iTableContentPorps {
   row: iIncomeTree
   setRow: (record: iIncomeTree) => void
 }
-interface iTemplateState {
-  attribution: string
-  children: iTemplateState[] | undefined
-  depth: number
-  id: number
-  isEnd: boolean
-  leaf: boolean
-  name: string
-  parentId: number
-  serial: string
-}
-interface iShowTemplateState {
-  isShow: boolean
-  template: string
-  loading: boolean
-}
+
 
 const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) => {
   const [ sectionTemplate, setSectionTemplate ] = useState<iShowTemplateState>({
@@ -80,7 +66,7 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
     initHandler()
   }, [])
   const initHandler = async () => {
-    const data  = await apiContractIncome(tenderStore.bid)
+    const data  = await apiContractExpenditure(tenderStore.bid)
     if (data.code === consts.RET_CODE.SUCCESS) {
       if (data.isTemplate && data.isTemplate === 1) {
         setSectionTemplate({
@@ -93,7 +79,7 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
           template2: data.sectionTemplate2
         })
       } else {
-        contractStore.updateTree(data.sectionTree.children)
+        contractPaidStore.updateTree(data.sectionTree.children)
       }
     }
   }
@@ -116,7 +102,7 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
       RET_CODE = code
     }
     if (RET_CODE === consts.RET_CODE.SUCCESS) {
-      contractStore.resetTree(tenderStore.tender.bidsectionId)
+      contractPaidStore.resetTree(tenderStore.tender.bidsectionId)
     }
   }
 
@@ -203,8 +189,8 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
       render: (text:any, record: iIncomeTree) => record.contractCode ? <span>{text}</span> : ''
     },
     {
-      title: '回款金额',
-      dataIndex: 'contractReturned',
+      title: '支出金额',
+      dataIndex: 'contractPaid',
       align: 'right',
       // eslint-disable-next-line react/display-name
       render: (text:any, record: iIncomeTree) => record.contractCode ? <span>{text}</span> : ''
@@ -213,7 +199,7 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
       title: '状态',
       dataIndex: 'contractStatus',
       // eslint-disable-next-line react/display-name
-      render: (_: any, record: iIncomeTree) => record.contractCode ? <span><i className={contractConsts[record.contractStatus].className}></i>{contractConsts[record.contractStatus].text}</span> : ''
+      render: (_: any, record: iIncomeTree) => record.contractCode ? <span className={contractConsts[record.contractStatus].className}>{contractConsts[record.contractStatus].text}</span> : ''
     }
 
   ]
@@ -231,7 +217,7 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
       })
       return message.error('请选择项目节模板!')
     }
-    const { code = -1 } = await apiSetTemplate(sectionTemplate.template, tenderStore.tender.bidsectionId)
+    const { code = -1 } = await apiSetTemplate(sectionTemplate.template, tenderStore.tender.bidsectionId, 1)
     if (code === consts.RET_CODE.SUCCESS) {
       await initHandler()
     }
@@ -257,7 +243,7 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
         rowClickHandler(record.id, record.bidsectionId, record.isEdit, record.isNew)
       },
       onDoubleClick() {
-        contractStore.rowChange(row.id)
+        contractPaidStore.rowChange(row.id)
       }
     }
   }
@@ -265,10 +251,10 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
   // 行点击回调
   const rowClickHandler = async (id: string, bid: string, isEdit?: boolean, isNew?: boolean) => {
     if (!isEdit && !isNew) {
-      const { code = -1, section = {}, contract: newContract = {} } = await apiGetIncome(id, bid)
+      const { code = -1, section = {}, contract: newContract = {} } = await apiGetExpenditure(id, bid)
       if (code === consts.RET_CODE.SUCCESS) {
         setRow(section)
-        contractStore.updateContract(newContract)
+        contractPaidStore.updateContract(newContract)
       }
     }
   }
@@ -276,16 +262,16 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
     return record.id === row.id ? 'ant-table-row-selected' : ''
   }
   const tabOnClick = (key: string) => {
-    contractStore.changeUpdate(key)
+    contractPaidStore.changeUpdate(key)
   }
 
   // 阿里oss上传弹窗
   const onShow = (show: boolean) => setVisible(show)
   const onCreate = async (fileList: iFile[]) => {
-    const { code = -1 } = await apiSaveFileInfo(fileList, consts.DATA_TYPE.CONTRACT, row.contractId)
+    const { code = -1 } = await apiSaveFileInfo(fileList, consts.DATA_TYPE.CONTRACT_PAID, row.contractId)
     if (code === consts.RET_CODE.SUCCESS) {
       setVisible(false)
-      contractStore.changeUpdate('3')
+      contractPaidStore.changeUpdate('3')
     }
   }
 
@@ -352,9 +338,9 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
   <div className={styles.spreadContent}>
     <div className={styles.spreadSheets}>
       {
-        contractStore.showTable ?
+        contractPaidStore.showTable ?
           <Table<iIncomeTree>
-            dataSource={contractStore.tree}
+            dataSource={contractPaidStore.tree}
             columns={TableColumns}
             bordered
             pagination={false}
@@ -376,30 +362,30 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
         tabBarExtraContent={{ right:
           <div className="pi-mg-right-5 pi-flex-row">
             {
-              contractStore.contract.id && contractStore.contract.status === contractConsts.status.checking ?
+              contractPaidStore.contract.id && contractPaidStore.contract.status === contractConsts.status.checking ?
               <>
                 <Button type="primary" size="small" onClick={() => modalHandler('update')} className="pi-mg-right-5">编辑合同</Button>
-                <Button type="primary" size="small" onClick={() => modalHandler('return')} className="pi-mg-right-5">添加回款</Button>
+                <Button type="primary" size="small" onClick={() => modalHandler('return')} className="pi-mg-right-5">添加支出</Button>
                 <Button type="primary" size="small" onClick={() => setVisible(true)}>上传文件</Button>
               </>
               : ''
             }
             {
-              contractStore.contract.id && contractStore.contract.status === contractConsts.status.willClose ?
+              contractPaidStore.contract.id && contractPaidStore.contract.status === contractConsts.status.willClose ?
               <Button type="primary" size="small" danger className="pi-mg-right-3" onClick={() => modalHandler('close')}>关闭合同</Button>
               : ''
             }
             {
-              contractStore.contract.id && contractStore.contract.status === contractConsts.status.closed ?
+              contractPaidStore.contract.id && contractPaidStore.contract.status === contractConsts.status.closed ?
               <ZhSubmitButton type="primary" size="small" danger className="pi-mg-right-3" onClick={() => modalHandler('unlock')}>解锁合同</ZhSubmitButton>
               : ''
             }
           </div>
         }}>
         <TabPane key="1" tab="合同详情">
-          <Detail {...contractStore.contract}></Detail>
+          <Detail {...contractPaidStore.contract}></Detail>
         </TabPane>
-        <TabPane key="2" tab="合同回款">
+        <TabPane key="2" tab="合同支出">
           <Receivable></Receivable>
         </TabPane>
         <TabPane key="3" tab="合同文件">

+ 21 - 10
src/pages/Contract/Content/Spending/components/Tabs/Detail/index.tsx

@@ -1,27 +1,38 @@
 import { iContractState } from '@/types/contract'
 import { contractConsts } from '@/utils/common/constStatus'
-import { dayjsFormat } from '@/utils/util'
-import React from 'react'
+import { dayjsFormat, formatMoney } from '@/utils/util'
+import { Tooltip } from 'antd'
+import React, { useMemo } from 'react'
 import styles from './index.module.scss'
 export default function Detail(props: iContractState) {
-  return (
+  const progress = useMemo(() => {
+    const i = parseFloat((parseFloat(props.returned) / parseFloat(props.price)).toFixed(1))
+    const j: number = 1 - i
+    const k: number = parseFloat(props.price) - parseFloat(props.returned)
+    return { returned: isNaN(i) ? '0%' : i * 100 + '%', unReturned: isNaN(j) ? '100%' : j * 100 + '%', unReturnedMoney: k }
+  }, [ props.id ])
+  return props.id ? (
     <div className={styles.detailTab}>
       <table className={styles.detailTable}>
         <tbody>
         <tr><th style={{ width: '10%' }}>项目内容</th><td style={{ width: '40%' }}>{props.content}</td><th style={{ width: '10%' }}>合同名称</th><td style={{ width: '40%' }}>{props.name}</td></tr>
-        <tr><th>合同编号</th><td>{props.code}</td><th>状态</th><td><i className={contractConsts[props.status].class}></i>{contractConsts[props.status].text}</td></tr>
+        <tr><th>合同编号</th><td>{props.code}</td><th>状态</th><td><span className={contractConsts[props.status].className}>{contractConsts[props.status].text}</span></td></tr>
         <tr><th>合同金额</th><td>{props.price}</td><th>创建时间</th><td>{props.createTime}</td></tr>
-        <tr><th>回款金额</th><td>{props.paid}</td><th>未回款金额</th><td>2,769,700.00</td></tr>
-          <tr><th>回款进度</th><td className={styles.progressContainer} colSpan={3}><div className={styles.progressContent}>
-            <div className={[ styles.progressBar, 'pi-bg-success' ].join(' ')} style={{ width: '57.7%' }} >57.7%</div>
-            <div className={[ styles.progressBar, 'pi-bg-gray' ].join(' ')} style={{ width: '42.3%' }}>42.3%</div>
+        <tr><th>支付金额</th><td>{formatMoney(parseFloat(props.returned))}</td><th>未支付金额</th><td>{formatMoney(progress.unReturnedMoney)}</td></tr>
+          <tr><th>支付进度</th><td className={styles.progressContainer} colSpan={3}><div className={styles.progressContent}>
+            <Tooltip title={`已支付:¥ ${formatMoney(parseFloat(props.returned))}`}>
+              <div className={[ styles.progressBar, 'pi-bg-success' ].join(' ')} style={{ width: progress.returned }}>{progress.returned}</div>
+            </Tooltip>
+            <Tooltip title={`未支付:¥ ${formatMoney(progress.unReturnedMoney)}`}>
+              <div className={[ styles.progressBar, 'pi-bg-gray' ].join(' ')} style={{ width: progress.unReturned }}>{progress.unReturned}</div>
+            </Tooltip>
           </div></td></tr>
         <tr><th>甲方</th><td>{props.partyA}</td><th>甲方签约人</th><td>{props.partyASigner}</td></tr>
         <tr><th>乙方</th><td>{props.partyB}</td><th>乙方签约人</th><td>{props.partyBSigner}</td></tr>
-        <tr><th>签约日期</th><td colSpan={3}>{props.signerTime && dayjsFormat(props.signerTime, 'YYYY-MM-DD')}</td></tr>
+        <tr><th>签约日期</th><td colSpan={3}>{dayjsFormat(props.signerTime, 'YYYY-MM-DD')}</td></tr>
         <tr><th>备注</th><td colSpan={3}>{props.remarks}</td></tr>
         </tbody>
       </table>
     </div>
-  )
+  ) : null
 }

+ 8 - 8
src/pages/Contract/Content/Spending/components/Tabs/File/index.tsx

@@ -1,4 +1,4 @@
-import { contractStore } from '@/store/mobx'
+import { contractPaidStore } from '@/store/mobx'
 import { apiDelFile, apiGetFileList } from '@/utils/common/api'
 import consts from '@/utils/consts'
 import { dayjsFormat } from '@/utils/util'
@@ -19,18 +19,18 @@ const File:React.FC<{}> = () => {
   const [ total, setTotal ] = useState<number>(0)
   const [ id, setId ] = useState<string>('')
   useEffect(() => {
-    if (contractStore.contract.id) {
-      if (contractStore.contract.id !== id) {
-        setId(contractStore.contract.id)
+    if (contractPaidStore.contract.id) {
+      if (contractPaidStore.contract.id !== id) {
+        setId(contractPaidStore.contract.id)
         initData()
-      } else if (contractStore.shouldUpdate && contractStore.shouldUpdate === '3') {
+      } else if (contractPaidStore.shouldUpdate && contractPaidStore.shouldUpdate === '3') {
         initData()
       }
-      contractStore.shouldUpdate && (contractStore.changeUpdate(''))
+      contractPaidStore.shouldUpdate && (contractPaidStore.changeUpdate(''))
     }
-  }, [ contractStore.contract.id, contractStore.shouldUpdate ])
+  }, [ contractPaidStore.contract.id, contractPaidStore.shouldUpdate ])
   const initData = async(pageNo: number = 1, pageSize: number = 7) => {
-    const { code = -1, data = [], total = 0 } = await apiGetFileList(consts.DATA_TYPE.CONTRACT, contractStore.contract.id, pageNo, pageSize)
+    const { code = -1, data = [], total = 0 } = await apiGetFileList(consts.DATA_TYPE.CONTRACT_PAID, contractPaidStore.contract.id, pageNo, pageSize)
     if (code === consts.RET_CODE.SUCCESS) {
       setData(data)
       setTotal(total)

+ 8 - 8
src/pages/Contract/Content/Spending/components/Tabs/Receivable/api.ts

@@ -6,8 +6,8 @@ import request from '@/utils/common/request'
  * @param contractsId 合同id
  * @param bidsectionId 标段id
  */
-export async function apiDelReturn(id: string, contractsId: string, bidsectionId: string) {
-  const { data } = await request.del('/api/contract/return/delete', { id, contractsId, bidsectionId })
+export async function apiDelPaid(id: string, contractsId: string, bidsectionId: string) {
+  const { data } = await request.del('/api/contract/paid/delete', { id, contractsId, bidsectionId })
   return data
 }
 
@@ -16,16 +16,16 @@ export async function apiDelReturn(id: string, contractsId: string, bidsectionId
  * @param constractsId 合同id
  * @param bidsectionId 标段id
  */
-export async function apiGetReturns(contractsId: string, bidsectionId: string) {
-  const { data } = await request.get('/api/contract/return/list', { contractsId, bidsectionId, page: 1 })
+export async function apiGetPaids(contractsId: string, bidsectionId: string) {
+  const { data } = await request.get('/api/contract/paid/list', { contractsId, bidsectionId, page: 1 })
   return data
 }
 
 /**
  * 获取回款类型
  */
-export async function apiGetReturnWay() {
-  const { data } = await request.get('/api/contract/return/way')
+export async function apiGetPaidWay() {
+  const { data } = await request.get('/api/contract/paid/way')
   return data
 }
 
@@ -33,7 +33,7 @@ export async function apiGetReturnWay() {
  * 更新回款内容
  * @param payload 载荷
  */
-export async function apiUpdateReturn(payload: object) {
-  const { data } = await request.post('/api/contract/return/update', payload)
+export async function apiUpdatePaid(payload: object) {
+  const { data } = await request.post('/api/contract/paid/update', payload)
   return data
 }

+ 17 - 49
src/pages/Contract/Content/Spending/components/Tabs/Receivable/index.tsx

@@ -1,6 +1,7 @@
 import DatePicker from '@/components/DatePicker'
 import FileModal from '@/components/FileModal'
-import { contractStore } from '@/store/mobx'
+import { contractPaidStore } from '@/store/mobx'
+import { iReceivableState, iEditableCellProps } from '@/types/contract'
 import { iFileModal } from '@/types/file'
 import consts from '@/utils/consts'
 import { dayjsFormat } from '@/utils/util'
@@ -10,35 +11,9 @@ import locale from 'antd/es/date-picker/locale/zh_CN'
 import dayjs from 'dayjs'
 import { observer } from 'mobx-react'
 import React, { useEffect, useMemo, useState } from 'react'
-import { apiDelReturn, apiGetReturns, apiUpdateReturn } from './api'
+import { apiDelPaid, apiGetPaids, apiUpdatePaid } from './api'
 
-interface iReceivableState {
-  accountId: string;
-  annexes: number;
-  bidsectionId: string;
-  contractsId: string;
-  createTime: string;
-  createUser: string;
-  fileCounts: number;
-  id: string;
-  page: number;
-  price: string;
-  projectId: string;
-  remarks: string;
-  time: string;
-  way: string;
-}
-interface EditableCellProps extends React.HTMLAttributes<HTMLElement> {
-  editing: boolean;
-  dataIndex: string;
-  title: any;
-  cellType: 'DatePicker' | 'text';
-  record: iReceivableState;
-  index: number;
-  children: React.ReactNode;
-}
-
-const EditableCell: React.FC<EditableCellProps> = ({
+const EditableCell: React.FC<iEditableCellProps> = ({
   editing,
   dataIndex,
   title,
@@ -75,13 +50,13 @@ const Receivable:React.FC<{}> = () => {
   const [ id, setId ] = useState<string>('')
   const [ fileModal, setFileModal ] = useState<iFileModal>({
     visible: false,
-    dataType: consts.DATA_TYPE.RETURN,
+    dataType: consts.DATA_TYPE.PAID,
     dataId: ''
   })
   const [ editingKey, setEditingKey ] = useState<string>('')
 
   const delConfirm = async (id: string, contractsId: string, bidsectionId: string) => {
-    const { code = -1 } = await apiDelReturn(id, contractsId, bidsectionId)
+    const { code = -1 } = await apiDelPaid(id, contractsId, bidsectionId)
     if (code === consts.RET_CODE.SUCCESS) {
       const newData = data.filter(item => item.id !== id)
       setData(newData)
@@ -89,18 +64,18 @@ const Receivable:React.FC<{}> = () => {
   }
 
   useEffect(() => {
-    if (contractStore.contract.id) {
-      if (contractStore.contract.id !== id) {
-        setId(contractStore.contract.id)
+    if (contractPaidStore.contract.id) {
+      if (contractPaidStore.contract.id !== id) {
+        setId(contractPaidStore.contract.id)
         initData()
-      } else if (contractStore.shouldUpdate && contractStore.shouldUpdate === '2') {
+      } else if (contractPaidStore.shouldUpdate && contractPaidStore.shouldUpdate === '2') {
         initData()
       }
-      contractStore.shouldUpdate && (contractStore.changeUpdate(''))
+      contractPaidStore.shouldUpdate && (contractPaidStore.changeUpdate(''))
     }
-  }, [ contractStore.contract.id, contractStore.shouldUpdate ])
+  }, [ contractPaidStore.contract.id, contractPaidStore.shouldUpdate ])
   const initData = async() => {
-    const { code = -1, data = [] } = await apiGetReturns(contractStore.contract.id, contractStore.contract.bidsectionId)
+    const { code = -1, data = [] } = await apiGetPaids(contractPaidStore.contract.id, contractPaidStore.contract.bidsectionId)
       if (code === consts.RET_CODE.SUCCESS) {
         setData(data)
       }
@@ -113,10 +88,8 @@ const Receivable:React.FC<{}> = () => {
       const index = newData.findIndex(item => key === item.id)
       if (index > -1) {
         const item = newData[index]
-        console.log(row)
-        console.log(item)
         const payload = { ...row, time: dayjsFormat(row.time, 'YYYY-MM-DD'), createTime: dayjsFormat(row.createTime, 'YYYY-MM-DD'), id: item.id, bidsectionId: item.bidsectionId, contractsId: item.contractsId }
-        const { code = -1 } = await apiUpdateReturn(payload)
+        const { code = -1 } = await apiUpdatePaid(payload)
         if (code === consts.RET_CODE.SUCCESS) {
           newData.splice(index, 1, {
             ...item,
@@ -126,11 +99,6 @@ const Receivable:React.FC<{}> = () => {
         }
       }
       setEditingKey('')
-      // else {
-      //   newData.push(row)
-      //   setData(newData)
-      //   setEditingKey('')
-      // }
     } catch (errInfo) {
       console.log('Validate Failed:', errInfo)
     }
@@ -147,7 +115,7 @@ const Receivable:React.FC<{}> = () => {
       }
     },
     {
-      title: '回款日期',
+      title: '支付日期',
       dataIndex: 'time',
       editable: true,
       width: '12%',
@@ -155,7 +123,7 @@ const Receivable:React.FC<{}> = () => {
       render: (text: string) => <span>{dayjsFormat(text, 'YYYY-MM-DD')}</span>
     },
     {
-      title: '回款金额',
+      title: '支付金额',
       dataIndex: 'price',
       editable: true,
       width: '12%',
@@ -163,7 +131,7 @@ const Receivable:React.FC<{}> = () => {
       render: (text: string) => <span className="pi-text-right pi-width-100P">{text}</span>
     },
     {
-      title: '回款方式',
+      title: '支付方式',
       dataIndex: 'way',
       editable: true,
       width: '12%'

+ 13 - 14
src/pages/Contract/Content/Spending/index.tsx

@@ -3,11 +3,10 @@ import Slot from '@/components/Header/slot'
 import RuleModal from '@/components/RuleModal'
 import SvgIcon from '@/components/SvgIcon'
 import { apiSaveRule } from '@/pages/Safe/Content/List/api'
-import { contractStore, tenderStore } from '@/store/mobx'
+import { contractPaidStore, tenderStore } from '@/store/mobx'
 import { iIncomeTree, iModalBooleanProps } from '@/types/contract'
 import { contractTreeBaseId } from '@/utils/common/constStatus'
 import consts from '@/utils/consts'
-import { SettingOutlined } from '@ant-design/icons'
 import { Button, message, Tooltip } from 'antd'
 import React, { useMemo, useState } from 'react'
 import { apiResfulContract, apiResfulContractTree } from './api'
@@ -19,7 +18,7 @@ interface iModal {
   loading: boolean
 }
 
-export default function Income() {
+export default function Expenditure() {
   const [ modalObj, setModalObj ] = useState<iModalBooleanProps>({
     type: '',
     visible: false,
@@ -59,12 +58,12 @@ export default function Income() {
       ...modalObj,
       confirmLoading: true
     })
-    const { code = -1 } = await apiResfulContract(type, values)
+    const { code = -1, contract = {} } = await apiResfulContract(type, values)
     if (code === consts.RET_CODE.SUCCESS) {
-      // contractStore.updateContract(section)
-      contractStore.resetTree(tenderStore.bid)
-      if (type === 'return') {
-        contractStore.changeUpdate('2')
+      contractPaidStore.updateContract(contract)
+      contractPaidStore.resetTree(tenderStore.bid)
+      if (type === 'paid') {
+        contractPaidStore.changeUpdate('2')
       }
     }
     setModalObj({
@@ -83,11 +82,11 @@ export default function Income() {
   }
   const treeResfulApiHandler = async (type: string, payload: any) => {
     if (type === 'add') {
-      return contractStore.addRowTree(payload.id)
+      return contractPaidStore.addRowTree(payload.id)
     }
     const { code = -1, section = {} } = await apiResfulContractTree(type, payload)
     if ( code === consts.RET_CODE.SUCCESS) {
-      contractStore.resetTree(tenderStore.bid)
+      contractPaidStore.resetTree(tenderStore.bid)
       if (type !== 'add' && type !== 'del') {
         setRow({ ...row, ...section })
       }
@@ -96,7 +95,7 @@ export default function Income() {
 
   const onRuleCreate = async (ruleValue: any) => {
     setRuleModal({ ...ruleModal, loading: true })
-    const { code = -1 } = await apiSaveRule({ bidsectionId: tenderStore.bid, type: 'contract_rule', rule: ruleValue })
+    const { code = -1 } = await apiSaveRule({ bidsectionId: tenderStore.bid, type: 'contract_paid_rule', rule: ruleValue })
     if (code === consts.RET_CODE.SUCCESS) {
       message.success("规则更改成功!")
     }
@@ -170,16 +169,16 @@ export default function Income() {
           </div>
         </Slot>
         <Slot position="right">
-          <Button type="ghost" size="small" icon={<SettingOutlined />} className="pi-mg-right-3" style={{ color: '#007bff' }} onClick={() => setRuleModal({ ...ruleModal, visible: true })}>设置</Button>
+          <Button type="ghost" size="small" icon={<SvgIcon type="xxh-cog" />} className="pi-mg-right-3" style={{ color: '#007bff' }} onClick={() => setRuleModal({ ...ruleModal, visible: true })}>设置</Button>
           {
-            showCBtn ? <Button type="primary" size="small" onClick={() => setModalObj({ ...modalObj, type: 'create', visible: true })}>新建收入合同</Button>
+            showCBtn ? <Button type="primary" size="small" onClick={() => setModalObj({ ...modalObj, type: 'create', visible: true })}>新建支出合同</Button>
             : ""
           }
         </Slot>
       </Header>
       <TableContent modalHandler={modalHandler} row={row} setRow={(record: iIncomeTree) => setRow({ ...row, ...record })}></TableContent>
       <RuleModal
-        type={consts.RULE.CONTRACT}
+        type={consts.RULE.CONTRACT_PAID}
         title="合同管理编号设置"
         visible={ruleModal.visible}
         onCreate={onRuleCreate}

+ 2 - 9
src/pages/Login/index.tsx

@@ -24,15 +24,9 @@ class NormalLoginForm extends Component<iLoginProps, iState> {
     this.state = initLoginState
   }
   onFinish = (values: iFromValues) => {
-    // const { code = -1, data }  = await apiLogin(values)
-    // if (code === consts.RET_CODE.SUCCESS) {
-      // }
     userStore.login(values)
   }
 
-  componentWillUnmount() {
-    // this.props.saveUser!(this.state.user)
-  }
   handleProjectCode = async (e: any) => {
     const projectCode = e.target?.value
     const { code = -1, data = [] } = await apiProject(projectCode)
@@ -57,7 +51,7 @@ class NormalLoginForm extends Component<iLoginProps, iState> {
       <Form
         name="normal_login"
         className={styles.loginForm}
-        initialValues={{ remember: true }}
+        initialValues={{ password: '123456', code : '234' }}
         onFinish={this.onFinish}
       >
         <h4>纵横工程建设项目管理系统</h4>
@@ -78,8 +72,7 @@ class NormalLoginForm extends Component<iLoginProps, iState> {
           name="password"
           rules={[ { required: true, message: 'Please input your Password!' } ]}
         >
-          <Input.Password type="password" placeholder="密码"
-          />
+          <Input.Password type="password" placeholder="密码"/>
         </Form.Item>
 
         <Form.Item>

+ 21 - 12
src/pages/Quality/Content/Info/Detail/api.ts

@@ -1,22 +1,31 @@
-import { iFile } from "@/types/file"
+import { iAuditHistoryState } from "@/types/safe"
 import request from "@/utils/common/request"
 
 /**
  * 获取安全巡检详情
  * @param id - 安全巡检id
  */
-export async function apiGetSafeDetail(id: string) {
-  const { data } = await request.get('/api/safe/detail', { id })
+export async function apiGetQualityDetail(id: string) {
+  const { data } = await request.get('/api/quality/detail', { id })
+  const auditHistory: iAuditHistoryState[][] = []
+  for (const key in data?.data?.auditHistory) {
+    if (Object.prototype.hasOwnProperty.call(data?.data?.auditHistory, key)) {
+      const history = data?.data?.auditHistory[key]
+      auditHistory.unshift(history)
+    }
+  }
+  data.data.auditHistory = auditHistory
   return data
 }
 
-/**
- *
- * @param fileList - 附件数组
- * @param dataType - 类型
- * @param dataId - 数据id
- */
-export async function apiSaveFileInfo(fileList: iFile[], dataType: number, dataId: string) {
-  const { data } = await request.post('/api/file', { fileList, dataType, dataId })
-  return data
+// 安全巡检审批
+export async function apiResfulQualityAudit(type: string, payload: object) {
+  if (type === 'delete') {
+    // 删除巡检记录
+    const { data } = await request.del('/api/quality', payload)
+    return data
+  } else {
+    const { data } = await request.post(`/api/quality_audit/${type}`, payload)
+    return data
+  }
 }

+ 328 - 0
src/pages/Quality/Content/Info/Detail/components/Modal/index.tsx

@@ -0,0 +1,328 @@
+import { GroupItem } from '@/components/AuditContent'
+import { ZhAuditBackButton, ZhButton, ZhCloseButton, ZhSubmitButton } from '@/components/Button'
+import { userStore } from '@/store/mobx'
+import { iAuditor, iLatestAuditorState } from '@/types/safe'
+import { iAccountGroupItem, iUserInfo } from '@/types/setting'
+import { getUserGroup } from '@/utils/common/user'
+import { Button, Form, Input, message, Modal, Popover } from 'antd'
+import React, { ChangeEvent, useEffect, useMemo, useState } from 'react'
+interface iAuditModalProps {
+  visible: boolean
+  onCancel: () => void
+  type: string
+  auditors: iAuditor[]
+  onCreate: (values?: any) => void
+  curAuditor: iLatestAuditorState
+}
+const textObj = {
+  start: {
+    title: '提交审批',
+    okText: '确认提交'
+  },
+  delete: {
+    title: '删除巡检',
+    okText: '确认删除'
+  },
+  close: {
+    title: '审批关闭',
+    okText: '确认关闭'
+  },
+  back: {
+    title: '审批退回',
+    okText: '确认退回'
+  },
+  pass: {
+    title: '审批通过',
+    okText: '确认通过'
+  }
+}
+
+interface iModalObjState {
+  searchValue: string
+  visible: boolean
+  auditType: string
+}
+const AuditModal: React.FC<iAuditModalProps> = props => {
+  const [ form ] = Form.useForm()
+  const { visible, type, onCancel, onCreate, auditors, curAuditor } = props
+  const [ modal, setModal ] = useState<iModalObjState>({
+    searchValue: '',
+    visible: false,
+    auditType: ''
+  })
+  // 是否是审批组的最后一个审核人
+  const isLastAuditor = useMemo(() => {
+    const len = auditors.filter(item => item.progress === '0').length
+    if (len && auditors.filter(item => item.progress === '0')[len - 1].audit_id === userStore.userInfo.id) {
+      return true
+    }
+    return false
+  }, [ auditors ])
+  const [ groups, setGroups ] = useState<Array<iAccountGroupItem>>([])
+  const [ user, setUser ] = useState<iUserInfo>({
+    account: '',
+    accountGroup: 0,
+    company: '',
+    csrf: '',
+    enable: 0,
+    id: '',
+    isAdmin: 0,
+    mobile: '',
+    name: '',
+    password: '',
+    position: '',
+    projectId: '',
+    role: '',
+    telephone: ''
+  })
+  const comfirmBtnClick = () => {
+    form.validateFields().then(values => {
+      form.resetFields()
+      if (user.id) {
+        values.audit_id = user.id
+      }
+
+      if (type === 'pass' && isLastAuditor && curAuditor.progress === '0' && !user.id) {
+        return message.error('请指定整改人!')
+      }
+      onCreate(values)
+    })
+  }
+
+  useEffect(() => {
+    if (visible && isLastAuditor && type === 'pass') {
+      initGroupList()
+    }
+    if (visible && type === 'back') {
+      if (curAuditor.progress === '1') {
+        const len = auditors.filter(item => item.progress === '0').length
+        const lastChecker = auditors.filter(item => item.progress === '0')[len - 1]
+        setUser({ ...user, id: lastChecker.id, name: lastChecker.name })
+      } else {
+        const newGroup = initAuditBackGroup()
+        setGroups(newGroup)
+      }
+    }
+  }, [ visible ])
+  const initGroupList = async (serach?: string) => {
+    const data = await getUserGroup(serach)
+    setGroups(data)
+  }
+
+  // 初始化审批退回下拉选择框
+  const initAuditBackGroup = () => {
+    console.log(auditors)
+
+    const newGroup: iAccountGroupItem[] = []
+    for (let index = 0; index < 3; index++) {
+      if (index === 0) {
+        const newAuditors = auditors
+          .filter(item => item.progress === '')
+          .map(item => {
+            return mapUser(item.name, '', item.position, item.company, item.mobile)
+          })
+        newGroup.push({  value: '检查人', children: newAuditors })
+      }
+      if (index === 1) {
+        const newAuditors = auditors
+          .filter(item => item.progress === '0')
+          .map(item => {
+            return mapUser(item.name, item.id, item.position, item.company, item.mobile)
+          })
+        newGroup.push({  value: '审批', children: newAuditors })
+      }
+      if (index === 2) {
+        const newAuditors = auditors
+          .filter(item => item.progress === '1')
+          .map(item => {
+            return mapUser(item.name, item.id, item.position, item.company, item.mobile)
+          })
+        newGroup.push({ value: '整改', children: newAuditors })
+      }
+
+    }
+    function mapUser(name: string, id: string, position: string, company: string, mobile: string) {
+      return {
+        account: '',
+        accountGroup: 0,
+        company,
+        csrf: '',
+        enable: 0,
+        id,
+        isAdmin: 0,
+        mobile,
+        name,
+        password: '',
+        position,
+        projectId: '',
+        role: '',
+        telephone: ''
+      }
+    }
+    return newGroup
+  }
+
+  const renderOkBtn = (type: string) => {
+    if (type === 'start' || type === 'pass') {
+      return (
+        <ZhSubmitButton size="small" onClick={comfirmBtnClick}>
+          {textObj[type]?.okText}
+        </ZhSubmitButton>
+      )
+    } else if (type === 'delete' || type === 'close') {
+      return (
+        <Button danger size="small" onClick={comfirmBtnClick}>
+          {textObj[type]?.okText}
+        </Button>
+      )
+    } else if (type === 'back') {
+      return (
+        <ZhAuditBackButton size="small" onClick={comfirmBtnClick}>
+          {textObj[type]?.okText}
+        </ZhAuditBackButton>
+      )
+    }
+  }
+
+  const search = (value: string) => {
+    if (value != modal.searchValue) {
+      setModal({ ...modal, searchValue: value })
+      initGroupList(value)
+    }
+  }
+
+  const change = (e: ChangeEvent) => {
+    e.persist()
+    const target = e.target as HTMLTextAreaElement
+    if (!target.value) {
+      initGroupList()
+    }
+  }
+
+  const itemSelectHandler = (item: iUserInfo, type: string = '') => {
+    setUser({ ...user, ...item })
+
+    setModal({ ...modal, visible: false, auditType: type })
+  }
+
+  const handleVisibleChange = (visible: boolean) => {
+    setModal({ ...modal, visible })
+  }
+
+  const showPopover = () => {
+    setModal({ ...modal, visible: true })
+  }
+
+  return (
+    <Modal
+      visible={visible}
+      title={textObj[type]?.title}
+      onCancel={onCancel}
+      getContainer={false}
+      footer={
+        <div className="pi-justify-end">
+          <ZhCloseButton size="small" onClick={onCancel} className="pi-mg-right-5">
+            关闭
+          </ZhCloseButton>
+          {renderOkBtn(type)}
+        </div>
+      }>
+      <Form form={form} layout="vertical">
+        {type === 'back' ? (
+          <>
+            <Form.Item name="opinion" label="审批意见">
+              <Input.TextArea rows={5}></Input.TextArea>
+            </Form.Item>
+            {curAuditor.progress !== '1' ? (
+              <Popover
+                content={groups.map(item => (
+                  <GroupItem {...item} key={item.value} onSelect={(item: iUserInfo, type?: string) => itemSelectHandler(item, type)}></GroupItem>
+                ))}
+                overlayClassName="popover-card"
+                trigger="click"
+                visible={modal.visible}
+                onVisibleChange={visible => handleVisibleChange(visible)}
+                placement="bottomLeft">
+                <ZhButton size="small" onClick={showPopover}>
+                  选择退回流程
+                </ZhButton>
+              </Popover>
+            ) : null}
+
+            {user.name ? (
+              <div className="pi-bordered pi-warning">
+                <span>已选择退回流程: </span>
+                <span>{user.name}</span>
+              </div>
+            ) : null}
+          </>
+        ) : null}
+        {type === 'delete' ? (
+          <>
+            <p className="mb-2">删除后,数据无法恢复,请谨慎操作。</p>
+            <p className="mb-2">
+              请在下方文本框输入文本「<span className="pi-red">确认删除本次巡检</span>」,以此确认删除操作。
+            </p>
+            <Form.Item
+              name="warningText"
+              rules={[
+                () => ({
+                  validator(rule, value) {
+                    if (!value || value !== '确认删除本次巡检') {
+                      return Promise.reject('请按照提示信息进行删除操作!')
+                    }
+                    return Promise.resolve()
+                  }
+                })
+              ]}>
+              <Input placeholder="输入文本, 确认删除"></Input>
+            </Form.Item>
+          </>
+        ) : null}
+        {type === 'start' ? <p>请确认审批流程及信息无误。</p> : null}
+        {type === 'close' ? (
+          <>
+            <Form.Item name="opinion" label="审批意见">
+              <Input.TextArea rows={5}></Input.TextArea>
+            </Form.Item>
+            <p className="pi-warning">审批关闭,将直接停止该巡检流程。</p>
+          </>
+        ) : null}
+        {type === 'pass' ? (
+          <>
+            <Form.Item name="opinion" label="审批意见">
+              <Input.TextArea rows={5}></Input.TextArea>
+            </Form.Item>
+            {isLastAuditor && curAuditor.progress === '0' ? (
+              <Popover
+                title={<Input.Search size="small" placeholder="姓名/手机 检索" onSearch={search} onChange={e => change(e)}></Input.Search>}
+                content={groups.map(item => (
+                  <GroupItem {...item} key={item.value} onSelect={(item: iUserInfo, type?: string) => itemSelectHandler(item, type)}></GroupItem>
+                ))}
+                overlayClassName="popover-card"
+                trigger="click"
+                visible={modal.visible}
+                onVisibleChange={visible => handleVisibleChange(visible)}
+                placement="bottomLeft">
+                <ZhButton size="small" onClick={showPopover}>
+                  指派整改人
+                </ZhButton>
+              </Popover>
+            ) : null}
+
+            {user.id ? (
+              <p className="pi-bordered pi-pd-8 pi-mg-top-5">
+                <span>已指派整改人: </span>
+                <span>
+                  {user.name}-{user.position}-{user.company}
+                </span>
+              </p>
+            ) : null}
+          </>
+        ) : null}
+      </Form>
+    </Modal>
+  )
+}
+
+export default AuditModal

+ 2 - 0
src/pages/Quality/Content/Info/Detail/index.module.scss

@@ -1,6 +1,8 @@
 .detailContainer {
   width: 100%;
+  height: calc(100vh - 34px);
   margin: 0 auto;
+  overflow-y: scroll;
   .content {
     flex: 0 0 66.666667%;
     max-width: 66.666667%;

+ 375 - 104
src/pages/Quality/Content/Info/Detail/index.tsx

@@ -1,92 +1,263 @@
-import { ZhSubmitButton, ZhUploadButton } from '@/components/Button'
+import AuditContent from '@/components/AuditContent'
+import { ZhAuditBackButton, ZhCloseButton, ZhSubmitButton, ZhUploadButton } from '@/components/Button'
 import DatePicker from '@/components/DatePicker'
 import Header from '@/components/Header'
 import Slot from '@/components/Header/slot'
 import OssUploadModal from '@/components/OssUpload'
 import SvgIcon from '@/components/SvgIcon'
 import { userStore } from '@/store/mobx'
+import { iDetailState } from '@/types/auditDetail'
 import { iFile } from '@/types/file'
-import { apiGetFileList, apiSaveFileInfo } from '@/utils/common/api'
+import { iAuditor } from '@/types/safe'
+import { iUserInfo } from '@/types/setting'
+import { apiDelFile, apiGetFileList, apiSaveFileInfo } from '@/utils/common/api'
+import { auditProgress } from '@/utils/common/constStatus'
 import consts from '@/utils/consts'
 import { dayjsFormat } from '@/utils/util'
-import { Button, Input, Pagination, Tooltip } from 'antd'
+import { Button, Input, message, Pagination, Tooltip } from 'antd'
 import locale from 'antd/es/date-picker/locale/zh_CN'
 import dayjs from 'dayjs'
-import React, { useEffect, useState } from 'react'
+import React, { useEffect, useState, useMemo } from 'react'
 import { RouteComponentProps } from 'react-router'
-import { apiGetSafeDetail } from './api'
+import { apiGetQualityDetail, apiResfulQualityAudit } from './api'
+import AuditModal from './components/Modal'
 import styles from './index.module.scss'
 const { TextArea } = Input
-interface iDetailState {
-  auditName: string
-  auditors: any[]
-  bidsectionId: string
-  code: string
-  createTime: string | undefined
-  demand: string
-  file: iFileState
-  id: string
-  inspection: string
-  inspectionDetail: string
-  position: string
-  status: number
+interface iModalObj {
+  ossModal: boolean
+  auditModal: boolean
+  auditType: string
+  curPage: number
 }
-interface iFileState {
-  fileList: any[]
-  total: number
-}
-const Detail:React.FC<RouteComponentProps> = (props) => {
-  const [ visible, setVisible ] = useState<boolean>(false)
+const Detail: React.FC<RouteComponentProps> = props => {
+  const { saveId = '' } = props.location.state as any
+  const [ visible, setVisible ] = useState<iModalObj>({
+    ossModal: false,
+    auditModal: false,
+    auditType: '',
+    curPage: 1
+  })
   const [ detail, setDetail ] = useState<iDetailState>({
-    auditName: "",
+    auditName: '',
+    uid: '',
     auditors: [],
-    bidsectionId: "",
-    code: "",
-    createTime: "",
-    demand: "",
+    bidsectionId: '',
+    code: '',
+    createTime: new Date().toDateString(),
+    demand: '',
     file: { fileList: [], total: 0 },
-    id: "",
-    inspection: "",
-    inspectionDetail: "",
-    position: "",
-    status: 0
+    id: '',
+    inspection: '',
+    inspectionDetail: '',
+    position: '',
+    status: 0,
+    checkOrder: {
+      // 整改单
+      name: '',
+      status: 0,
+      opinion: '',
+      create_time: new Date().toDateString()
+    },
+    auditHistory: [],
+    rectifiedInfo: [],
+    latestAuditor: {
+      audit_id: '',
+      audit_order: 0,
+      bidsection_id: '',
+      data_id: '',
+      data_type: 0,
+      id: '',
+      progress: '',
+      project_id: '',
+      status: 0
+    },
+    times: 0
   })
-
+  const isEdited = useMemo(() => {
+    return !!detail.status
+  }, [ detail.status ])
   useEffect(() => {
     initData()
-  }, [])
-  const initData = async() => {
-    const { saveId = "" } = props.location.state as any
-    const { code = -1, data = {} } = await apiGetSafeDetail(saveId)
+  }, [ saveId ])
+  const initData = async () => {
+    const { code = -1, data = {} } = await apiGetQualityDetail(saveId)
     if (code === consts.RET_CODE.SUCCESS) {
       setDetail({ ...detail, ...data })
     }
+    if (!userStore.groupList.length) {
+      userStore.getGroupList()
+    }
   }
   const onCreate = async (fileList: iFile[]) => {
-    const { code = -1 } = await apiSaveFileInfo(fileList, consts.DATA_TYPE.SAFE, detail.id)
+    const { code = -1 } = await apiSaveFileInfo(fileList, consts.DATA_TYPE.QUALITY, detail.id)
     if (code === consts.RET_CODE.SUCCESS) {
-      const newFiles = detail.file.fileList.concat(fileList.map(file => {
-        return { ...file, accountName: userStore.userInfo.name }
-      }))
-      setDetail({ ...detail, file: { ...detail.file, fileList: newFiles } })
-      setVisible(false)
+      const newFiles = detail.file.fileList.concat(
+        fileList.map(file => {
+          return { ...file, accountName: userStore.userInfo.name }
+        })
+      )
+      setDetail({ ...detail, file: { ...detail.file, total: newFiles.length } })
+      await fileListChange(visible.curPage)
     }
   }
-  const onShow = (show: boolean) => setVisible(show)
+  const onOssModalShow = (show: boolean) => setVisible({ ...visible, ossModal: show })
   const fileListChange = async (pageNo: number = 1, pageSize: number = 10) => {
-    const { code = -1, data } = await apiGetFileList(consts.DATA_TYPE.SAFE,detail.id, pageNo, pageSize)
+    const { code = -1, data, total } = await apiGetFileList(consts.DATA_TYPE.QUALITY, detail.id, pageNo, pageSize)
+    if (code === consts.RET_CODE.SUCCESS) {
+      setVisible({ ...visible, curPage: pageNo, ossModal: false })
+      setDetail({ ...detail, file: { ...detail.file, fileList: data, total } })
+    }
+  }
+
+  const delFile = async (id: string, isLast: boolean) => {
+    const { code = -1 } = await apiDelFile(id)
     if (code === consts.RET_CODE.SUCCESS) {
-      setDetail({ ...detail, file: { ...detail.file, fileList: data } })
+      console.log(visible.curPage - 1)
+      await fileListChange(isLast ? visible.curPage - 1 : visible.curPage)
+      isLast && (setVisible({ ...visible, curPage: visible.curPage - 1 }))
+    }
+  }
+
+  const addAuditor = (type: string, user: iUserInfo) => {
+    if (detail.auditors.find(item => item.progress === (type === 'check' ? '0' : '2') && item.audit_id === user.id)) {
+      return message.error('该审批组下已存在该审批人,请勿重复添加!')
+    }
+    if (type === 'check') {
+      const newAuditors = detail.auditors
+      const len = detail.auditors.filter((item: iAuditor) => item.progress === '0').length
+      newAuditors.push({
+        id: '',
+        mobile: '',
+        audit_id: user.id,
+        audit_order: len + 1,
+        position: user.position,
+        progress: '0',
+        name: user.name,
+        accountGroup: user.accountGroup,
+        company: user.company,
+        status: 0
+      })
+      setDetail({ ...detail, auditors: newAuditors })
+    } else {
+      const newAuditors = detail.auditors
+      const len = detail.auditors.filter((item: iAuditor) => item.progress === '2').length
+      newAuditors.push({
+        id: '',
+        audit_id: user.id,
+        mobile: '',
+        audit_order: len + 1,
+        position: user.position,
+        progress: '2',
+        name: user.name,
+        accountGroup: user.accountGroup,
+        company: user.company,
+        status: 0
+      })
+      setDetail({ ...detail, auditors: newAuditors })
     }
   }
+
+  const delAuditor = (id: string, progress: string) => {
+    const newAuditors = detail.auditors.filter(item => item.progress !== progress)
+    const auditor = detail.auditors.find(item => item.progress === progress && item.audit_id !== id)
+
+    if (auditor) {
+      newAuditors.push(auditor)
+    }
+    setDetail({ ...detail, auditors: newAuditors })
+  }
+
+  const btnClick = (type: string) => {
+    setVisible({ ...visible, auditType: type, auditModal: true })
+  }
+  const onModalConfirm = (values?: object) => {
+    let payload: any = { quality_id: detail.id, bidsection_id: detail.bidsectionId, ...values }
+    if (visible.auditType === 'start') {
+      payload.auditors = detail.auditors.filter(item => item.progress === '0').map(item => item.audit_id)
+      payload.reAuditors = detail.auditors.filter(item => item.progress === '2').map(item => item.audit_id)
+      payload.times = detail.times
+      if (!payload.auditors.length || !payload.reAuditors.length) {
+        return message.error('审批人或复查人不能为空!')
+      }
+    }
+
+    if (visible.auditType === 'delete') {
+      payload = { id: detail.id }
+    }
+
+    if (visible.auditType === 'pass' || visible.auditType === 'back') {
+      payload.id = detail.latestAuditor.id
+      if (detail.latestAuditor.progress === '1') {
+        if (!detail.checkOrder.opinion) {
+          return message.error('请填写整改单!')
+        } else {
+          payload.rectifiedInfo = detail.checkOrder.opinion
+        }
+      }
+    }
+
+    if (visible.auditType === 'close') {
+      payload.id = detail.latestAuditor.id
+    }
+    apiResful(visible.auditType, payload)
+  }
+  const apiResful = async (type: string, payload: any) => {
+    const { code } = await apiResfulQualityAudit(type, payload)
+    if (code === consts.RET_CODE.SUCCESS) {
+      setVisible({ ...visible, auditModal: false })
+      if (type === 'delete') {
+        props.history.goBack()
+      } else {
+        initData()
+      }
+    }
+  }
+  const renderHeaderBtn = (status: number) => {
+    if (!detail.latestAuditor.audit_id && userStore.userInfo.id !== detail.uid) return null
+    if (detail.latestAuditor.audit_id && userStore.userInfo.id !== detail.latestAuditor.audit_id) return null
+    if (!status) {
+      return (
+        <div className="pi-flex-row pi-align-center">
+          <ZhCloseButton size="small" onClick={() => btnClick('delete')}>
+            删除巡检
+          </ZhCloseButton>
+          <ZhSubmitButton size="small" className="pi-mg-left-5" onClick={() => btnClick('start')}>
+            提交审批
+          </ZhSubmitButton>
+        </div>
+      )
+    } else if (status === auditProgress.checking || status === auditProgress.checkNo) {
+      return (
+        <div className="pi-flex-row pi-align-center">
+          <Button type="primary" danger size="small" onClick={() => btnClick('close')}>
+            关闭
+          </Button>
+          <ZhAuditBackButton size="small" className="pi-mg-left-5" onClick={() => btnClick('back')}>
+            审批退回
+          </ZhAuditBackButton>
+          <ZhSubmitButton size="small" className="pi-mg-left-5" onClick={() => btnClick('pass')}>
+            审批通过
+          </ZhSubmitButton>
+        </div>
+      )
+    } else if (status === auditProgress.checked) {
+      return (
+        <div className="pi-flex-row pi-align-center">
+          <ZhAuditBackButton size="small" onClick={() => btnClick('back')}>
+            审批退回
+          </ZhAuditBackButton>
+          <ZhSubmitButton size="small" className="pi-mg-left-5" onClick={() => btnClick('pass')}>
+            整改完成
+          </ZhSubmitButton>
+        </div>
+      )
+    }
+  }
+
   return (
     <div className="wrap-contaniner">
-      <Header title="安全巡检">
-        <Slot position="right">
-          <div>
-            <ZhSubmitButton size="small">提交审批</ZhSubmitButton>
-          </div>
-        </Slot>
+      <Header title="质量巡检">
+        <Slot position="right">{renderHeaderBtn(detail.status)}</Slot>
       </Header>
       <div className={styles.detailContainer}>
         <div className={styles.content}>
@@ -94,78 +265,178 @@ const Detail:React.FC<RouteComponentProps> = (props) => {
           <table className="pi-table pi-bordered">
             <thead>
               <tr>
-                <th colSpan={2} className="pi-text-center">安全巡检单</th>
+                <th colSpan={2} className="pi-text-center">
+                  质量巡检单
+                </th>
               </tr>
             </thead>
             <tbody>
-              <tr><th style={{ width: "150px" }}>检查项目</th><td><TextArea value={detail.inspection}></TextArea></td></tr>
-              <tr><th style={{ width: "150px" }}>现场检查情况</th><td><TextArea value={detail.inspectionDetail}></TextArea></td></tr>
-              <tr><th style={{ width: "150px" }}>处理要求及措施</th><td><TextArea value={detail.demand}></TextArea></td></tr>
               <tr>
-                <th style={{ width: "150px" }}>检查日期</th>
-                <td><DatePicker size="small" locale={locale} allowClear={false} value={dayjs(detail.createTime)} onChange={(value) => setDetail({ ...detail, createTime: value?.format() })}></DatePicker></td>
+                <th style={{ width: '150px' }}>检查项目</th>
+                <td>{isEdited ? <span>{detail.inspection}</span> : <TextArea value={detail.inspection}></TextArea>}</td>
+              </tr>
+              <tr>
+                <th style={{ width: '150px' }}>现场检查情况</th>
+                <td>{isEdited ? <span>{detail.inspectionDetail}</span> : <TextArea value={detail.inspectionDetail}></TextArea>}</td>
+              </tr>
+              <tr>
+                <th style={{ width: '150px' }}>处理要求及措施</th>
+                <td>{isEdited ? <span>{detail.demand}</span> : <TextArea value={detail.demand}></TextArea>}</td>
+              </tr>
+              <tr>
+                <th style={{ width: '150px' }}>检查日期</th>
+                <td>
+                  {isEdited ? (
+                    <span>{detail.createTime && dayjsFormat(detail.createTime, 'YYYY-MM-DD')}</span>
+                  ) : (
+                    <DatePicker
+                      size="small"
+                      locale={locale}
+                      allowClear={false}
+                      value={dayjs(detail.createTime)}
+                      onChange={value => setDetail({ ...detail, createTime: value?.format() })}></DatePicker>
+                  )}
+                </td>
+              </tr>
+              <tr>
+                <th style={{ width: '150px' }}>质检员</th>
+                <td>{detail.auditName}</td>
               </tr>
-              <tr><th style={{ width: "150px" }}>质检员</th><td>{detail.auditName}</td></tr>
             </tbody>
-
           </table>
+          {detail.status === auditProgress.checked && detail.latestAuditor.progress === '1' ? (
+            <table className="pi-table pi-bordered">
+              <thead>
+                <tr>
+                  <th colSpan={2} className="pi-text-center">
+                    整改单
+                  </th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr>
+                  <th style={{ width: '150px' }}>整改情况</th>
+                  <td>
+                    <TextArea
+                      value={detail.checkOrder.opinion}
+                      onChange={e => setDetail({ ...detail, checkOrder: { ...detail.checkOrder, opinion: e.currentTarget.value } })}></TextArea>
+                  </td>
+                </tr>
+                <tr>
+                  <th style={{ width: '150px' }}>整改日期</th>
+                  <td>
+                    <DatePicker
+                      size="small"
+                      locale={locale}
+                      allowClear={false}
+                      value={dayjs(detail.checkOrder.create_time)}
+                      onChange={value => setDetail({ ...detail, checkOrder: { ...detail.checkOrder, create_time: value?.format() } })}></DatePicker>
+                  </td>
+                </tr>
+                <tr>
+                  <th style={{ width: '150px' }}>整改人</th>
+                  <td>{detail.auditors.find(item => item.progress === '1')?.name}</td>
+                </tr>
+              </tbody>
+            </table>
+          ) : (
+            detail.rectifiedInfo.map(item => (
+              <table className="pi-table pi-bordered" key={item.create_time}>
+                <thead>
+                  <tr>
+                    <th colSpan={2} className="pi-text-center">
+                      整改单
+                    </th>
+                  </tr>
+                </thead>
+                <tbody>
+                  <tr>
+                    <th style={{ width: '150px' }}>整改情况</th>
+                    <td>{item.opinion}</td>
+                  </tr>
+                  <tr>
+                    <th style={{ width: '150px' }}>整改日期</th>
+                    <td>{dayjsFormat(item.create_time, 'YYYY-MM-DD')}</td>
+                  </tr>
+                  <tr>
+                    <th style={{ width: '150px' }}>整改人</th>
+                    <td>{item.name}</td>
+                  </tr>
+                </tbody>
+              </table>
+            ))
+          )}
           <table className="pi-table pi-bordered mt-3">
             <thead>
               <tr>
                 <th></th>
                 <th className="pi-text-center">附件</th>
                 <th className="pi-text-center">上传者</th>
-                <th className="pi-text-center" style={{ width: 200 }}>上传时间</th>
+                <th className="pi-text-center" style={{ width: 200 }}>
+                  上传时间
+                </th>
                 <th className="pi-text-center">操作</th>
               </tr>
             </thead>
             <tbody>
-              <tr><td colSpan={5}><ZhUploadButton size="small" icon={<SvgIcon type="xxh-cloud-upload"/>} onClick={() => setVisible(true)}>上传附件</ZhUploadButton></td></tr>
+              <tr>
+                <td colSpan={5}>
+                  <ZhUploadButton size="small" icon={<SvgIcon type="xxh-cloud-upload" />} onClick={() => setVisible({ ...visible, ossModal: true })}>
+                    上传附件
+                  </ZhUploadButton>
+                </td>
+              </tr>
 
-              {
-                detail.file.fileList?.map((file, idx) => {
-                  return (
-                    <tr key={idx}>
-                      <td className="pi-width-70">{idx+1}</td>
-                      <td style={{ width: 383, maxWidth: 383, overflow: 'hidden', whiteSpace: 'nowrap',textOverflow:'ellipsis' }}>
-                        <a href={consts.OSS_PATH.REVIEW + file.filepath} target="_blank" rel="noopener noreferrer">{file.filename}</a>
-                      </td>
-                      <td className="pi-text-center">{file.accountName}</td><td className="pi-text-center">{dayjsFormat(file.createTime)}</td>
-                      <td className="pi-text-center pi-width-90">
-                        <Tooltip title="移除">
-                          <Button size="small" type="text" icon={<SvgIcon type="xxh-times-circle1"/>} style={{ color: "#df3f45" }}></Button>
-                        </Tooltip>
-                      </td>
-                    </tr>
-                  )
-                })
-              }
-              <tr><td colSpan={5} className="pi-text-right">
-              {
-                detail.file.total ?
-                <Pagination
-                defaultCurrent={1}
-                size="small"
-                pageSize={consts.PAGE_SIZE}
-                hideOnSinglePage={true}
-                total={detail.file.total}
-                onChange={(page, pageSize) => fileListChange(page, pageSize)}
-                >
-              </Pagination>
-              : ''
-              }
-              </td></tr>
+              {detail.file.fileList?.map((file, idx) => (
+                <tr key={idx}>
+                <td className="pi-width-70">{idx + 1}</td>
+                <td style={{ width: 383, maxWidth: 383, overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }}>
+                  <a href={consts.OSS_PATH.REVIEW + file.filepath} target="_blank" rel="noopener noreferrer">
+                    {file.filename}
+                  </a>
+                </td>
+                <td className="pi-text-center">{file.accountName}</td>
+                <td className="pi-text-center">{dayjsFormat(file.createTime)}</td>
+                <td className="pi-text-center pi-width-90">
+                  <Tooltip title="移除">
+                    <Button size="small" type="text" icon={<SvgIcon type="xxh-times-circle1" />} style={{ color: '#df3f45' }} onClick={() => delFile(file.id, !idx)}></Button>
+                  </Tooltip>
+                </td>
+              </tr>
+              ))}
+              {detail.file.total > consts.PAGE_SIZE ? (
+                <tr>
+                  <td colSpan={5} className="pi-text-right">
+                    <Pagination
+                      current={visible.curPage}
+                      size="small"
+                      pageSize={consts.PAGE_SIZE}
+                      hideOnSinglePage={true}
+                      total={detail.file.total}
+                      onChange={(page, pageSize) => fileListChange(page, pageSize)}></Pagination>
+                  </td>
+                </tr>
+              ) : null}
             </tbody>
           </table>
+          <AuditContent
+            auditors={detail.auditors}
+            onSelect={addAuditor}
+            onDelete={delAuditor}
+            latest={detail.latestAuditor}
+            auditHistory={detail.auditHistory}
+            status={detail.status}
+            uName={detail.auditName}></AuditContent>
         </div>
       </div>
-      <OssUploadModal
-        visible={visible}
-        onCancel={() => setVisible(false)}
-        onCreate={onCreate}
-        onShow={onShow}
-        >
-      </OssUploadModal>
+      <OssUploadModal visible={visible.ossModal} onCancel={() => setVisible({ ...visible, ossModal: false })} onCreate={onCreate} onShow={onOssModalShow}></OssUploadModal>
+      <AuditModal
+        type={visible.auditType}
+        visible={visible.auditModal}
+        onCancel={() => setVisible({ ...visible, auditModal: false })}
+        onCreate={onModalConfirm}
+        auditors={detail.auditors}
+        curAuditor={detail.latestAuditor}></AuditModal>
     </div>
   )
 }

+ 4 - 4
src/pages/Quality/Content/List/api.ts

@@ -6,8 +6,8 @@ import request from "@/utils/common/request"
  * 获取当前标段的安全巡检列表
  * @param bidsectionId 标段id
  */
-export async function apiSafeList(bidsectionId: string, pageNo: number, pageSize: number) {
-  const { data } = await request.get('/api/safe', { bidsectionId, pageNo, pageSize })
+export async function apiQualityList(bidsectionId: string, pageNo: number, pageSize: number) {
+  const { data } = await request.get('/api/quality', { bidsectionId, pageNo, pageSize })
   return data
 }
 
@@ -15,8 +15,8 @@ export async function apiSafeList(bidsectionId: string, pageNo: number, pageSize
  * 创建新的安全巡检记录
  * @param payload 载荷
  */
-export async function apiCreateSafe(payload: iCreateSafe) {
-  const { data } = await request.post('/api/safe', payload)
+export async function apiCreateQuality(payload: iCreateSafe) {
+  const { data } = await request.post('/api/quality', payload)
   return data
 }
 

+ 17 - 21
src/pages/Quality/Content/List/index.tsx

@@ -4,18 +4,18 @@ import Slot from '@/components/Header/slot'
 import RuleModal from '@/components/RuleModal'
 import SvgIcon from '@/components/SvgIcon'
 import { tenderStore } from '@/store/mobx'
+import { iFileModal } from '@/types/file'
 import { iCreateSafe } from '@/types/safe'
 import { safeStatus } from '@/utils/common/constStatus'
 import consts from '@/utils/consts'
 import { dayjsFormat } from '@/utils/util'
-import { SettingOutlined } from '@ant-design/icons'
 import { Button, message, Table } from 'antd'
 import { ColumnsType } from 'antd/lib/table'
 import React, { useEffect, useState } from 'react'
 import { Link } from 'react-router-dom'
-import { apiCreateSafe, apiSafeList, apiSaveRule } from './api'
+import { apiCreateQuality, apiQualityList, apiSaveRule } from './api'
 import AddModel from './modal'
-interface iSafeList {
+interface iQualityList {
   id: string;
   code: string;
   createTime: string;
@@ -32,12 +32,8 @@ interface iModal {
   loading: boolean
 }
 
-interface iFileModal {
-  visible: boolean
-  dataType: number
-  dataId: string
-}
-const SafeList:React.FC<{}> =() => {
+
+const QualityList:React.FC<{}> =() => {
   const [ ruleModal, setRuleModal ] = useState<iModal>({
     visible: false,
     loading: false
@@ -48,13 +44,13 @@ const SafeList:React.FC<{}> =() => {
   })
   const [ fileModal, setFileModal ] = useState<iFileModal>({
     visible: false,
-    dataType: consts.DATA_TYPE.SAFE,
+    dataType: consts.DATA_TYPE.QUALITY,
     dataId: ''
   })
   useEffect(() => {
     initData()
   }, [])
-  const columns:ColumnsType<iSafeList> = [
+  const columns:ColumnsType<iQualityList> = [
     {
       title: '序号',
       // eslint-disable-next-line react/display-name
@@ -67,7 +63,7 @@ const SafeList:React.FC<{}> =() => {
       dataIndex: 'code',
       // eslint-disable-next-line react/display-name
       render: (text: string, record) => {
-        return <Link to={{ pathname: "/console/safe/content/detail/info", state: { saveId: record.id } }}>{text}</Link>
+        return <Link to={{ pathname: "/console/quality/content/detail/info", state: { saveId: record.id } }}>{text}</Link>
       }
     },
     {
@@ -111,7 +107,7 @@ const SafeList:React.FC<{}> =() => {
       }
     }
   ]
-  const [ list, setList ] = useState<iSafeList[]>([
+  const [ list, setList ] = useState<iQualityList[]>([
     {
       id: '',
       code: '',
@@ -127,7 +123,7 @@ const SafeList:React.FC<{}> =() => {
   ])
   const [ total, setTotal ] = useState<number>(0)
   const initData = (pageNo: number = 1, pageSize: number = consts.PAGE_SIZE) => {
-    apiSafeList(tenderStore.bid, pageNo, pageSize).then(({ code = -1, data = [], total = 0 }) => {
+    apiQualityList(tenderStore.bid, pageNo, pageSize).then(({ code = -1, data = [], total = 0 }) => {
       if (code === consts.RET_CODE.SUCCESS) {
         setList(data)
         setTotal(total)
@@ -139,7 +135,7 @@ const SafeList:React.FC<{}> =() => {
 
   const onRuleCreate = async (ruleValue: any) => {
     setRuleModal({ ...ruleModal, loading: true })
-    const { code = -1 } = await apiSaveRule({ bidsectionId: tenderStore.bid, type: 'safe_rule', rule: ruleValue })
+    const { code = -1 } = await apiSaveRule({ bidsectionId: tenderStore.bid, type: 'quality_rule', rule: ruleValue })
     if (code === consts.RET_CODE.SUCCESS) {
       message.success("规则更改成功!")
       initData()
@@ -149,7 +145,7 @@ const SafeList:React.FC<{}> =() => {
   const onAddCreate = async (payload: iCreateSafe) => {
     setAddModal({ ...addModal, loading: true })
     const createTime = dayjsFormat(payload.createTime)
-    const { code = -1 } = await apiCreateSafe({ ...payload, createTime })
+    const { code = -1 } = await apiCreateQuality({ ...payload, createTime })
     if (code === consts.RET_CODE.SUCCESS) {
       initData()
     }
@@ -158,11 +154,11 @@ const SafeList:React.FC<{}> =() => {
 
   return (
     <div className="wrap-contaniner">
-      <Header title="巡检概况">
+      <Header title="质量巡检">
         <Slot position="right">
           {
             !list.length ?
-            <Button type="ghost" size="small" icon={<SettingOutlined />} className="pi-mg-right-3" style={{ color: '#007bff' }} onClick={() => setRuleModal({ ...ruleModal, visible: true })}>设置</Button>
+            <Button type="ghost" size="small" icon={<SvgIcon type="xxh-cog" />} className="pi-mg-right-3" style={{ color: '#007bff' }} onClick={() => setRuleModal({ ...ruleModal, visible: true })}>设置</Button>
             : ""
           }
 
@@ -184,8 +180,8 @@ const SafeList:React.FC<{}> =() => {
         >
       </Table>
       <RuleModal
-        type={consts.RULE.SAFE}
-        title="安全巡检编号设置"
+        type={consts.RULE.QUALITY}
+        title="质量巡检编号设置"
         visible={ruleModal.visible}
         onCreate={onRuleCreate}
         loading={ruleModal.loading}
@@ -209,4 +205,4 @@ const SafeList:React.FC<{}> =() => {
   )
 }
 
-export default SafeList
+export default QualityList

+ 6 - 6
src/pages/Quality/Content/List/modal.tsx

@@ -6,14 +6,14 @@ import locale from 'antd/es/date-picker/locale/zh_CN'
 import React, { useEffect } from 'react'
 import { apiAutoCode } from './api'
 import styles from './index.module.scss'
-interface iSafeCreateFormProps {
+interface iQualityCreateFormProps {
   visible: boolean;
   loading: boolean;
   onCreate: (values: any) => void;
   onCancel: () => void;
 }
 
-const SafeCreateForm: React.FC<iSafeCreateFormProps> = ({
+const QualityCreateForm: React.FC<iQualityCreateFormProps> = ({
   visible,
   loading,
   onCreate,
@@ -21,7 +21,7 @@ const SafeCreateForm: React.FC<iSafeCreateFormProps> = ({
 }) => {
   const [ form ] = Form.useForm()
   const autoCodeHandler = async () => {
-    const { code = -1, data = "" } = await apiAutoCode(tenderStore.tender.bidsectionId, 'safeRule')
+    const { code = -1, data = "" } = await apiAutoCode(tenderStore.tender.bidsectionId, 'qualityRule')
     if (code === consts.RET_CODE.SUCCESS) {
       if (data) {
         const ruleArr: string[] = []
@@ -48,7 +48,7 @@ const SafeCreateForm: React.FC<iSafeCreateFormProps> = ({
     <Modal
       getContainer={false}
       visible={visible}
-      title="新建安全巡检"
+      title="新建质量巡检"
       okText="确认添加"
       cancelText="取消"
       onCancel={onCancel}
@@ -83,7 +83,7 @@ const SafeCreateForm: React.FC<iSafeCreateFormProps> = ({
           <Input placeholder="请填写巡检项"/>
         </Form.Item>
         <Form.Item name="createTime" label="日期" rules={[ { required: true, message: '请选择日期' } ]}>
-          <DatePicker locale={locale} allowClear style={{ width: '100%' }}></DatePicker>
+          <DatePicker locale={locale} allowClear className="pi-width-100P"></DatePicker>
         </Form.Item>
         <div className={styles.warningFooter}>添加后再补充完善其余信息</div>
       </Form>
@@ -92,4 +92,4 @@ const SafeCreateForm: React.FC<iSafeCreateFormProps> = ({
 }
 
 
-export default SafeCreateForm
+export default QualityCreateForm

+ 3 - 5
src/pages/Quality/List/index.tsx

@@ -16,6 +16,8 @@ import './index.scss'
 const List: React.FC<RouteComponentProps> = (props) => {
   const { clear } = useAliveController()
   useEffect(() => {
+    // 清除所有的缓存页面
+    clear()
     getTree()
   }, [])
   const [ tree, setTree ] = useState({
@@ -49,11 +51,7 @@ const List: React.FC<RouteComponentProps> = (props) => {
   const linkHandler = (id: string, name: string) => {
     tenderStore.saveTenderInfo({ bidsectionId: id, name })
     // tenderStore.saveName(name)
-    props.history.push('/console/safe/content/summary')
-    // 清除所有的缓存页面
-    // console.log(getCachingNodes())
-
-    clear()
+    props.history.push('/console/quality/content/summary')
   }
   const columns:ColumnsType<ContractTree> = [
     {

+ 1 - 1
src/pages/Safe/Content/Info/Detail/api.ts

@@ -11,7 +11,7 @@ export async function apiGetSafeDetail(id: string) {
   for (const key in data?.data?.auditHistory) {
     if (Object.prototype.hasOwnProperty.call(data?.data?.auditHistory, key)) {
       const history = data?.data?.auditHistory[key]
-      auditHistory.push(history)
+      auditHistory.unshift(history)
     }
   }
   data.data.auditHistory = auditHistory

+ 6 - 7
src/pages/Safe/Content/Info/Detail/components/Modal/index.tsx

@@ -85,10 +85,6 @@ const AuditModal: React.FC<iAuditModalProps> = props => {
       if (type === 'pass' && isLastAuditor && curAuditor.progress === '0' && !user.id) {
         return message.error('请指定整改人!')
       }
-
-      if (type === 'back') {
-        values.progress = modal.auditType
-      }
       onCreate(values)
     })
   }
@@ -101,7 +97,7 @@ const AuditModal: React.FC<iAuditModalProps> = props => {
       if (curAuditor.progress === '1') {
         const len = auditors.filter(item => item.progress === '0').length
         const lastChecker = auditors.filter(item => item.progress === '0')[len - 1]
-        setUser({ ...user, id: lastChecker.audit_id, name: lastChecker.name })
+        setUser({ ...user, id: lastChecker.id, name: lastChecker.name })
       } else {
         const newGroup = initAuditBackGroup()
         setGroups(newGroup)
@@ -115,13 +111,15 @@ const AuditModal: React.FC<iAuditModalProps> = props => {
 
   // 初始化审批退回下拉选择框
   const initAuditBackGroup = () => {
+    console.log(auditors)
+
     const newGroup: iAccountGroupItem[] = []
     for (let index = 0; index < 3; index++) {
       if (index === 0) {
         const newAuditors = auditors
           .filter(item => item.progress === '')
           .map(item => {
-            return mapUser(item.name, item.id, item.position, item.company, item.mobile)
+            return mapUser(item.name, '', item.position, item.company, item.mobile)
           })
         newGroup.push({  value: '检查人', children: newAuditors })
       }
@@ -141,6 +139,7 @@ const AuditModal: React.FC<iAuditModalProps> = props => {
           })
         newGroup.push({ value: '整改', children: newAuditors })
       }
+
     }
     function mapUser(name: string, id: string, position: string, company: string, mobile: string) {
       return {
@@ -250,7 +249,7 @@ const AuditModal: React.FC<iAuditModalProps> = props => {
               </Popover>
             ) : null}
 
-            {user.id ? (
+            {user.name ? (
               <div className="pi-bordered pi-warning">
                 <span>已选择退回流程: </span>
                 <span>{user.name}</span>

+ 112 - 85
src/pages/Safe/Content/Info/Detail/index.tsx

@@ -6,8 +6,9 @@ import Slot from '@/components/Header/slot'
 import OssUploadModal from '@/components/OssUpload'
 import SvgIcon from '@/components/SvgIcon'
 import { userStore } from '@/store/mobx'
+import { iDetailState } from '@/types/auditDetail'
 import { iFile } from '@/types/file'
-import { iAuditHistoryState, iAuditor, iLatestAuditorState, iRectifiedState } from '@/types/safe'
+import { iAuditor } from '@/types/safe'
 import { iUserInfo } from '@/types/setting'
 import { apiDelFile, apiGetFileList, apiSaveFileInfo } from '@/utils/common/api'
 import { auditProgress } from '@/utils/common/constStatus'
@@ -16,61 +17,33 @@ import { dayjsFormat } from '@/utils/util'
 import { Button, Input, message, Pagination, Tooltip } from 'antd'
 import locale from 'antd/es/date-picker/locale/zh_CN'
 import dayjs from 'dayjs'
-import React, { useEffect, useState } from 'react'
+import React, { useEffect, useState, useMemo } from 'react'
 import { RouteComponentProps } from 'react-router'
 import { apiGetSafeDetail, apiResfulSafeAudit } from './api'
 import AuditModal from './components/Modal'
 import styles from './index.module.scss'
 const { TextArea } = Input
-interface iDetailState {
-  times: number
-  auditName: string
-  auditors: iAuditor[]
-  bidsectionId: string
-  code: string
-  createTime: string | undefined
-  demand: string
-  file: iFileState
-  id: string
-  inspection: string
-  inspectionDetail: string
-  position: string
-  status: number
-  checkOrder: iModifiedOrder
-  auditHistory: iAuditHistoryState[][]
-  rectifiedInfo: iRectifiedState[]
-  latestAuditor: iLatestAuditorState
-}
-
-interface iModifiedOrder {
-  name: string
-  end_time: string | undefined
-  opinion: string
-  status: number
-}
-interface iFileState {
-  fileList: any[]
-  total: number
-}
-
 interface iModalObj {
   ossModal: boolean
   auditModal: boolean
   auditType: string
+  curPage: number
 }
 const Detail: React.FC<RouteComponentProps> = props => {
   const { saveId = '' } = props.location.state as any
   const [ visible, setVisible ] = useState<iModalObj>({
     ossModal: false,
     auditModal: false,
-    auditType: ''
+    auditType: '',
+    curPage: 1
   })
   const [ detail, setDetail ] = useState<iDetailState>({
     auditName: '',
+    uid: '',
     auditors: [],
     bidsectionId: '',
     code: '',
-    createTime: '',
+    createTime: new Date().toDateString(),
     demand: '',
     file: { fileList: [], total: 0 },
     id: '',
@@ -82,18 +55,26 @@ const Detail: React.FC<RouteComponentProps> = props => {
       name: '',
       status: 0,
       opinion: '',
-      end_time: ''
+      create_time: new Date().toDateString()
     },
     auditHistory: [],
     rectifiedInfo: [],
     latestAuditor: {
-      id: '',
       audit_id: '',
-      status: 0,
-      progress: ''
+      audit_order: 0,
+      bidsection_id: '',
+      data_id: '',
+      data_type: 0,
+      id: '',
+      progress: '',
+      project_id: '',
+      status: 0
     },
     times: 0
   })
+  const isEdited = useMemo(() => {
+    return !!detail.status
+  }, [ detail.status ])
   useEffect(() => {
     initData()
   }, [ saveId ])
@@ -114,23 +95,25 @@ const Detail: React.FC<RouteComponentProps> = props => {
           return { ...file, accountName: userStore.userInfo.name }
         })
       )
-      setDetail({ ...detail, file: { ...detail.file, fileList: newFiles } })
-      setVisible({ ...visible, ossModal: false })
+      setDetail({ ...detail, file: { ...detail.file, total: newFiles.length } })
+      await fileListChange(visible.curPage)
     }
   }
   const onOssModalShow = (show: boolean) => setVisible({ ...visible, ossModal: show })
   const fileListChange = async (pageNo: number = 1, pageSize: number = 10) => {
-    const { code = -1, data } = await apiGetFileList(consts.DATA_TYPE.SAFE, detail.id, pageNo, pageSize)
+    const { code = -1, data, total } = await apiGetFileList(consts.DATA_TYPE.SAFE, detail.id, pageNo, pageSize)
     if (code === consts.RET_CODE.SUCCESS) {
-      setDetail({ ...detail, file: { ...detail.file, fileList: data } })
+      setVisible({ ...visible, curPage: pageNo, ossModal: false })
+      setDetail({ ...detail, file: { ...detail.file, fileList: data, total } })
     }
   }
 
-  const delFile = async (id: string) => {
+  const delFile = async (id: string, isLast: boolean) => {
     const { code = -1 } = await apiDelFile(id)
     if (code === consts.RET_CODE.SUCCESS) {
-      const newFiles = detail.file.fileList.filter(file => file.id !== id)
-      setDetail({ ...detail, file: { ...detail.file, fileList: newFiles } })
+      console.log(visible.curPage - 1)
+      await fileListChange(isLast ? visible.curPage - 1 : visible.curPage)
+      isLast && (setVisible({ ...visible, curPage: visible.curPage - 1 }))
     }
   }
 
@@ -167,6 +150,10 @@ const Detail: React.FC<RouteComponentProps> = props => {
   const onModalConfirm = (values?: object) => {
     let payload: any = { safe_id: detail.id, bidsection_id: detail.bidsectionId, ...values }
     if (visible.auditType === 'start') {
+      payload.inspection = detail.inspection
+      payload.inspectionDetail = detail.inspectionDetail
+      payload.demand = detail.demand
+      payload.createTime = detail.createTime
       payload.auditors = detail.auditors.filter(item => item.progress === '0').map(item => item.audit_id)
       payload.reAuditors = detail.auditors.filter(item => item.progress === '2').map(item => item.audit_id)
       payload.times = detail.times
@@ -179,9 +166,15 @@ const Detail: React.FC<RouteComponentProps> = props => {
       payload = { id: detail.id }
     }
 
-    if (visible.auditType === 'pass') {
+    if (visible.auditType === 'pass' || visible.auditType === 'back') {
       payload.id = detail.latestAuditor.id
-      detail.latestAuditor.progress === '1' && (payload.rectifiedInfo = detail.checkOrder.opinion)
+      if (detail.latestAuditor.progress === '1') {
+        if (!detail.checkOrder.opinion) {
+          return message.error("请填写整改单!")
+        } else {
+          payload.rectifiedInfo = detail.checkOrder.opinion
+        }
+      }
     }
 
     if (visible.auditType === 'close') {
@@ -201,6 +194,9 @@ const Detail: React.FC<RouteComponentProps> = props => {
     }
   }
   const renderHeaderBtn = (status: number) => {
+
+    if (!detail.latestAuditor.audit_id && userStore.userInfo.id !== detail.uid) return null
+    if (detail.latestAuditor.audit_id && userStore.userInfo.id !== detail.latestAuditor.audit_id) return null
     if (!status) {
       return (
         <div className="pi-flex-row pi-align-center">
@@ -248,30 +244,40 @@ const Detail: React.FC<RouteComponentProps> = props => {
               <tr>
                 <th style={{ width: '150px' }}>检查项目</th>
                 <td>
-                  <TextArea value={detail.inspection}></TextArea>
+                  {
+                    isEdited ? <span>{detail.inspection}</span> : <TextArea value={detail.inspection} onChange={(e) => {setDetail({ ...detail, inspection: e.currentTarget.value })}}></TextArea>
+                  }
                 </td>
               </tr>
               <tr>
                 <th style={{ width: '150px' }}>现场检查情况</th>
                 <td>
-                  <TextArea value={detail.inspectionDetail}></TextArea>
+                  {
+                    isEdited ? <span>{detail.inspectionDetail}</span> : <TextArea value={detail.inspectionDetail} onChange={(e) => {setDetail({ ...detail, inspectionDetail: e.currentTarget.value })}}></TextArea>
+                  }
                 </td>
               </tr>
               <tr>
                 <th style={{ width: '150px' }}>处理要求及措施</th>
                 <td>
-                  <TextArea value={detail.demand}></TextArea>
+                  {
+                    isEdited ? <span>{detail.demand}</span> : <TextArea value={detail.demand} onChange={(e) => {setDetail({ ...detail, demand: e.currentTarget.value })}}></TextArea>
+                  }
                 </td>
               </tr>
               <tr>
                 <th style={{ width: '150px' }}>检查日期</th>
                 <td>
-                  <DatePicker
+                  {
+                    isEdited ? <span>{detail.createTime && dayjsFormat(detail.createTime, 'YYYY-MM-DD')}</span> :
+                    <DatePicker
                     size="small"
                     locale={locale}
                     allowClear={false}
                     value={dayjs(detail.createTime)}
                     onChange={value => setDetail({ ...detail, createTime: value?.format() })}></DatePicker>
+                  }
+
                 </td>
               </tr>
               <tr>
@@ -281,7 +287,7 @@ const Detail: React.FC<RouteComponentProps> = props => {
             </tbody>
           </table>
           {
-            detail.latestAuditor.progress === '1' && detail.rectifiedInfo.length ?
+            detail.status === auditProgress.checked && detail.latestAuditor.progress === '1'?
             <table className="pi-table pi-bordered">
             <thead>
               <tr>
@@ -304,17 +310,42 @@ const Detail: React.FC<RouteComponentProps> = props => {
                     size="small"
                     locale={locale}
                     allowClear={false}
-                    value={dayjs(detail.checkOrder.end_time)}
-                    onChange={value => setDetail({ ...detail, checkOrder: { ...detail.checkOrder, end_time: value?.format() } })}></DatePicker>
+                    value={dayjs(detail.checkOrder.create_time)}
+                    onChange={value => setDetail({ ...detail, checkOrder: { ...detail.checkOrder, create_time: value?.format() } })}></DatePicker>
                 </td>
               </tr>
               <tr>
                 <th style={{ width: '150px' }}>整改人</th>
-                <td>王五</td>
+                <td>{detail.auditors.find(item => item.progress === '1')?.name}</td>
+              </tr>
+            </tbody>
+          </table>
+          : detail.rectifiedInfo.map(item => (
+            <table className="pi-table pi-bordered" key={item.create_time}>
+            <thead>
+              <tr>
+                <th colSpan={2} className="pi-text-center">
+                  整改单
+                </th>
+              </tr>
+            </thead>
+            <tbody>
+              <tr>
+                <th style={{ width: '150px' }}>整改情况</th>
+                <td>{item.opinion}</td>
+              </tr>
+              <tr>
+                <th style={{ width: '150px' }}>整改日期</th>
+                <td>{dayjsFormat(item.create_time, 'YYYY-MM-DD')}</td>
+              </tr>
+              <tr>
+                <th style={{ width: '150px' }}>整改人</th>
+                <td>{item.name}</td>
               </tr>
             </tbody>
           </table>
-          : null
+          ))
+
           }
           <table className="pi-table pi-bordered mt-3">
             <thead>
@@ -337,40 +368,36 @@ const Detail: React.FC<RouteComponentProps> = props => {
                 </td>
               </tr>
 
-              {detail.file.fileList?.map((file, idx) => {
-                return (
-                  <tr key={idx}>
-                    <td className="pi-width-70">{idx + 1}</td>
-                    <td style={{ width: 383, maxWidth: 383, overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }}>
-                      <a href={consts.OSS_PATH.REVIEW + file.filepath} target="_blank" rel="noopener noreferrer">
-                        {file.filename}
-                      </a>
-                    </td>
-                    <td className="pi-text-center">{file.accountName}</td>
-                    <td className="pi-text-center">{dayjsFormat(file.createTime)}</td>
-                    <td className="pi-text-center pi-width-90">
-                      <Tooltip title="移除">
-                        <Button size="small" type="text" icon={<SvgIcon type="xxh-times-circle1" />} style={{ color: '#df3f45' }} onClick={() => delFile(file.id)}></Button>
-                      </Tooltip>
-                    </td>
-                  </tr>
-                )
-              })}
-              <tr>
-                <td colSpan={5} className="pi-text-right">
-                  {detail.file.total ? (
+              {detail.file.fileList?.map((file, idx) => (
+                <tr key={idx}>
+                <td className="pi-width-70">{idx + 1}</td>
+                <td style={{ width: 383, maxWidth: 383, overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }}>
+                  <a href={consts.OSS_PATH.REVIEW + file.filepath} target="_blank" rel="noopener noreferrer">
+                    {file.filename}
+                  </a>
+                </td>
+                <td className="pi-text-center">{file.accountName}</td>
+                <td className="pi-text-center">{dayjsFormat(file.createTime)}</td>
+                <td className="pi-text-center pi-width-90">
+                  <Tooltip title="移除">
+                    <Button size="small" type="text" icon={<SvgIcon type="xxh-times-circle1" />} style={{ color: '#df3f45' }} onClick={() => delFile(file.id, !idx)}></Button>
+                  </Tooltip>
+                </td>
+              </tr>
+              ))}
+              {detail.file.total > consts.PAGE_SIZE ? (
+                <tr>
+                  <td colSpan={5} className="pi-text-right">
                     <Pagination
-                      defaultCurrent={1}
+                      current={visible.curPage}
                       size="small"
                       pageSize={consts.PAGE_SIZE}
                       hideOnSinglePage={true}
                       total={detail.file.total}
                       onChange={(page, pageSize) => fileListChange(page, pageSize)}></Pagination>
-                  ) : (
-                    ''
-                  )}
-                </td>
-              </tr>
+                  </td>
+                </tr>
+              ) : null}
             </tbody>
           </table>
           <AuditContent

+ 10 - 0
src/pages/Safe/Content/Info/Summary/api.ts

@@ -0,0 +1,10 @@
+import request from "@/utils/common/request"
+
+/**
+ * 获取安全概述
+ * @param bid 标段id
+ */
+export async function apiGetSafeSurvey(bid: string) {
+  const { data } = await request.get('/api/safe/survey', { bidsectionId: bid })
+  return data
+}

+ 80 - 0
src/pages/Safe/Content/Info/Summary/columnChart.tsx

@@ -0,0 +1,80 @@
+import React, { memo } from 'react'
+import { DualAxes } from '@ant-design/charts'
+import { DualAxesConfig } from '@ant-design/charts/es/dualAxes'
+import { SafeColumnCharType, SafeLineChartType } from '.'
+
+interface iColumnChartProps {
+  columnData: SafeColumnCharType[]
+  lineData: SafeLineChartType[]
+}
+const ColumnChart: React.FC<{ data: iColumnChartProps }> = props => {
+  const config: DualAxesConfig = {
+    data: [ props.data.columnData, props.data.lineData ],
+    xField: 'month',
+    yField: [ 'count', 'percentage' ],
+    // xAxis: { label: { autoRotate: false } },
+    // scrollbar: { type: 'vertical' },
+    tooltip: {
+      formatter: datum => {
+        const obj = { name: '', value: '' }
+        if (datum.percentage) {
+          obj.name = '整改占总数比例'
+          obj.value = datum.percentage + '%'
+        } else if (datum.name && datum.name === 'rectifyed') {
+          obj.name = '完成整改'
+          obj.value = datum.count
+        } else {
+          obj.name = '提交巡检'
+          obj.value = datum.count
+        }
+        return obj
+      }
+    },
+    geometryOptions: [
+      {
+        geometry: 'column',
+        isGroup: true,
+        seriesField: 'name',
+
+        columnWidthRatio: 0.4,
+        color: ({ name }) => {
+          if (name === 'rectifyed') {
+            return '#D8CAAF'
+          }
+          return '#B5C4B1'
+        },
+        label: {}
+      },
+      {
+        geometry: 'line',
+        color: '#A06666',
+        isStack: true
+        // smooth: true
+      }
+    ],
+    legend: {
+      custom: true,
+      position: 'top',
+      items: [
+        {
+          value: 'submit',
+          name: '提交巡检',
+          marker: { symbol: 'square', style: { fill: '#B5C4B1', r: 5 } }
+        },
+        {
+          value: 'rectifyed',
+          name: '完成整改',
+          marker: { symbol: 'square', style: { fill: '#D8CAAF', r: 5 } }
+        },
+        {
+          value: 'bill',
+          name: '整改占总数比例',
+          marker: { symbol: 'square', style: { fill: '#A06666', r: 5 } }
+        }
+      ]
+    }
+  }
+  return <DualAxes {...config} />
+}
+
+export default memo(ColumnChart)

+ 14 - 0
src/pages/Safe/Content/Info/Summary/index.scss

@@ -15,3 +15,17 @@
 .card.h400 {
   height: 400px;
 }
+
+.echarts-pie {
+  width: 100%;
+  height: 300px;
+}
+
+.echarts-column-chart {
+  width: 100%;
+  height: 450px;
+  & > h5 {
+    margin: 0;
+    font-weight: 700;
+  }
+}

+ 99 - 26
src/pages/Safe/Content/Info/Summary/index.tsx

@@ -1,44 +1,117 @@
 import Header from '@/components/Header'
-import React from 'react'
-import { RouteProps } from 'react-router'
+import { tenderStore } from '@/store/mobx'
+import consts from '@/utils/consts'
+import dayjs from 'dayjs'
+import React, { useEffect, useState, useMemo } from 'react'
+import { useActivate } from 'react-activation'
+import { Link } from 'react-router-dom'
+import { apiGetSafeSurvey } from './api'
+import ColumnChart from './columnChart'
 import './index.scss'
+import PieChart from './pieChart'
+export interface SafeColumnCharType {
+  name: 'rectifyed' | 'submit'
+  month: string
+  count: number
+}
 
+export interface SafeLineChartType {
+  month: string
+  percentage: number
+}
+interface iRectifyData {
+  auditName: string
+  createTime: string
+  id: string
+  inspectionDetail: string
+  status: number
+}
+interface iSummaryState {
+  approvalTotal: number
+  rectifyTotal: number
+  rectifyedTotal: number
+  rectifylist: iRectifyData[]
+  columnarData: SafeColumnCharType[]
+  lineData: SafeLineChartType[]
+}
+const Summary: React.FC<{}> = () => {
+  useEffect(() => {
+    initData()
+  }, [])
+  useActivate(() => {
+    initData()
+  })
+  const [ state, setState ] = useState<iSummaryState>({
+    approvalTotal: 0,
+    rectifyTotal: 0,
+    rectifyedTotal: 0,
+    rectifylist: [],
+    columnarData: [],
+    lineData: []
+  })
+  useActivate(() => initData())
+  const initData = async () => {
+    const { data, code = -1 } = await apiGetSafeSurvey(tenderStore.bid)
+    if (code === consts.RET_CODE.SUCCESS) {
+      setState({ ...state, ...data })
+    }
+  }
 
-const Summary:React.FC<RouteProps> = (props) => {
-  // console.log(props.location?.state)
-
-  // useActivate(() => {
-  //   BidHander()
-  // })
-  // useEffect(() => {
-  //   BidHander()
-  // }, [])
-  // const BidHander = () => {
-  //   if (Object.keys(props.location?.state as object).length) {
-  //     // console.log(props.location?.state)
+  const pieData = useMemo(() => {
+    const data = [
+      {
+        type: '审批中',
+        value: state.approvalTotal
+      },
+      {
+        type: '整改中',
+        value: state.rectifyTotal
+      },
+      {
+        type: '已整改',
+        value: state.rectifyedTotal
+      }
+    ]
+    return data
+  }, [ state.approvalTotal, state.rectifyTotal, state.rectifyedTotal ])
 
-  //     const { id = '', name = '' } = props.location?.state as {id: string;name: string}
-  //     id && (tenderStore.saveBidsectionId(id))
-  //     name && (tenderStore.saveName(name))
-  //   }
-  // }
+  const columnCharData = useMemo(() => {
+    return { columnData: state.columnarData, lineData: state.lineData }
+  }, [ state.columnarData, state.lineData ])
+  const handleDate = (createTime: string) => {
+    return dayjs(new Date()).diff(dayjs(createTime), 'day') + '天'
+  }
   return (
     <div className="wrap-contaniner">
       <Header title="巡检概况"></Header>
       <div className="wrap-content m-3 pi-flex-column pi-justify-start">
         <div className="pi-justify-start">
           <div className="pi-flex-twice card pi-flex-column">
-            <header className="card-title">整改中 (23) </header>
+            <header className="card-title">整改中 ({state.rectifyTotal}) </header>
             <div>
-              <p>检查路面清洗,路面污染严重</p>
-              <p>检查路面清洗,路面污染严重</p>
-              <p>检查路面清洗,路面污染严重</p>
-              <p>检查路面清洗,路面污染严重</p>
+              {state.rectifylist.map(item => {
+                return (
+                  <div key={item.id} className="pi-justify-between pi-lh-18 pi-height-18">
+                    <Link to={{ pathname: '/console/safe/content/detail/info', state: { saveId: item.id } }}>{item.inspectionDetail}</Link>
+                    <div className="pi-align-center">
+                      <span className="pi-mg-right-5">{item.auditName}</span>
+                      <span className="pi-badge danger">{handleDate(item.createTime)}</span>
+                    </div>
+                  </div>
+                )
+              })}
             </div>
           </div>
-          <div className="pi-flex-treble pi-mg-left-30 card"></div>
+          <div className="pi-flex-treble pi-mg-left-30 card">
+            <PieChart data={pieData}></PieChart>
+          </div>
+        </div>
+        <div className="card echarts-column-chart mt-3">
+          <h5 className="pi-fz-25 pi-fw-5">整改趋势</h5>
+          <div className="pi-width-100P">
+            <ColumnChart data={columnCharData}></ColumnChart>
+          </div>
         </div>
-        <div className="card h-400 mt-3"></div>
       </div>
     </div>
   )

+ 46 - 0
src/pages/Safe/Content/Info/Summary/pieChart.tsx

@@ -0,0 +1,46 @@
+import React, { memo } from 'react'
+import { Pie, G2  } from '@ant-design/charts'
+import { PieConfig } from '@ant-design/charts/es/pie'
+import './index.scss'
+G2.registerTheme('custom-pie', {
+  colors10: [ '#C7B8A1', '#9CA8B8', '#7B8B6F' ],
+  colors29: [ '#DACAB1', '#ABB8CA', '#87987A' ]
+})
+
+type iPieChartProps = {
+  type: string
+  value: number
+}[]
+const DemoPie: React.FC<{data: iPieChartProps}> = ({ data }) => {
+  console.log(data)
+
+  const config:PieConfig = {
+    appendPadding: 10,
+    data,
+    padding: 'auto',
+    angleField: 'value',
+    colorField: 'type',
+    radius: 1,
+    renderer: 'canvas',
+    innerRadius: 0.7,
+    className: 'echarts-pie',
+    label: {
+      type: 'outer',
+      autoRotate: false,
+      content: '{name}: {percentage}'
+    },
+    interactions: [
+      // { type: 'element-selected' },
+      { type: 'pie-legend-active' }
+      // { type: 'element-active' }
+      // { type: 'pie-statistic-active' }
+    ],
+    legend: {
+      layout: 'horizontal',
+      position: 'top-left'
+    },
+    theme: 'custom-pie'
+  }
+  return <Pie {...config} />
+}
+export default memo(DemoPie)

+ 2 - 2
src/pages/Safe/Content/List/index.tsx

@@ -155,11 +155,11 @@ const SafeList:React.FC<{}> =() => {
 
   return (
     <div className="wrap-contaniner">
-      <Header title="巡检概况">
+      <Header title="安全巡检">
         <Slot position="right">
           {
             !list.length ?
-            <Button type="ghost" size="small" icon={<SettingOutlined />} className="pi-mg-right-3" style={{ color: '#007bff' }} onClick={() => setRuleModal({ ...ruleModal, visible: true })}>设置</Button>
+            <Button type="ghost" size="small" icon={<SvgIcon type="xxh-cog" />} className="pi-mg-right-3" style={{ color: '#007bff' }} onClick={() => setRuleModal({ ...ruleModal, visible: true })}>设置</Button>
             : ""
           }
 

+ 5 - 2
src/router/routes.ts

@@ -129,7 +129,10 @@ export const routeConfig: RouteModel[] = [
                     path: 'list',
                     component: AsyncModuleLoader(() => import('@/pages/Safe/Content/List')),
                     auth: [ 'USER', 'ADMIN' ],
-                    defaultChildRoute: true
+                    defaultChildRoute: true,
+                    meta: {
+                      noCache: true
+                    }
                   },
                   {
                     path: 'info',
@@ -182,7 +185,7 @@ export const routeConfig: RouteModel[] = [
                 component: AsyncModuleLoader(() => import('@/pages/Quality/Content/Info')),
                 auth: [ 'USER', 'ADMIN' ],
                 meta: {
-                  title: '安全巡检'
+                  title: '质量巡检'
                 },
                 childRoutes: [
                   {

+ 69 - 0
src/store/mobx/contractPaid/index.ts

@@ -0,0 +1,69 @@
+import { apiContractExpenditure } from "@/pages/Contract/Content/Spending/components/Modal/api"
+import { iContractState, iIncomeTree } from "@/types/contract"
+import consts from "@/utils/consts"
+import { action, computed, observable } from "mobx"
+import { lookupNode, nodeCheck } from "../contractReturn"
+class Contract {
+
+  @observable tree: iIncomeTree[] = []
+  @observable contract: iContractState = {
+    bidsectionId: "",
+    code: "",
+    content: "",
+    contractsType: 0,
+    createTime: "",
+    id: "",
+    name: "",
+    paid: "0.00",
+    partyA: "",
+    partyASigner: "",
+    partyB: "",
+    partyBSigner: "",
+    price: "0.00",
+    projectId: "",
+    remarks: "",
+    returned: "0.00",
+    signerTime: "",
+    status: 0,
+    treeId: "",
+    updateTime: ""
+  }
+  @observable shouldUpdate: string = ''
+
+  @action changeUpdate(i: string) {
+    this.shouldUpdate = i
+
+  }
+  @action updateTree(tree: iIncomeTree[]) {
+    this.tree = tree
+  }
+
+  @action resetTree(id: string) {
+    apiContractExpenditure(id).then(({ code = -1, sectionTree = {} }) => {
+      if (code === consts.RET_CODE.SUCCESS && Object.keys(sectionTree).length) {
+        this.tree = sectionTree.children
+      }
+    })
+  }
+
+  @action updateContract(data: iContractState) {
+    this.contract = data
+  }
+
+  // 增加行
+  @action addRowTree(id: string) {
+    const newTree = lookupNode(id, this.tree)
+    this.tree = newTree
+  }
+
+  // 行点击
+  @action rowChange(id: string) {
+    const newTree = nodeCheck(id, this.tree)
+    this.tree = newTree
+  }
+  @computed get showTable() {
+    return this.tree && this.tree.length > 1
+  }
+}
+
+export default new Contract()

+ 2 - 2
src/store/mobx/contract/index.ts

@@ -90,7 +90,7 @@ class Contract {
   }
 }
 
-function lookupNode(id: string, tree: iIncomeTree[]) {
+export function lookupNode(id: string, tree: iIncomeTree[]) {
   return tree.map((item:iIncomeTree) => {
     item.isEdit = false
     if (item.id === id) {
@@ -108,7 +108,7 @@ function lookupNode(id: string, tree: iIncomeTree[]) {
 }
 
 // 项目节被选中更改isEdit的值
-function nodeCheck(id: string, tree: iIncomeTree[]) {
+export function nodeCheck(id: string, tree: iIncomeTree[]) {
   return tree.map((item: iIncomeTree) => {
     item.isEdit = false
     if (item.id === id) {

+ 4 - 2
src/store/mobx/index.ts

@@ -1,4 +1,5 @@
-import contractStore from './contract'
+import contractReturnStore from './contractReturn'
+import contractPaidStore from './contractPaid'
 import frameStore from './frame'
 import tenderStore from './tender'
 import userStore from './user'
@@ -6,6 +7,7 @@ export {
   userStore,
   frameStore,
   tenderStore,
-  contractStore
+  contractReturnStore,
+  contractPaidStore
 }
 

+ 33 - 0
src/types/auditDetail.d.ts

@@ -0,0 +1,33 @@
+import { iAuditHistoryState, iLatestAuditorState, iRectifiedState } from "./safe"
+
+export interface iDetailState {
+  times: number
+  auditName: string
+  auditors: iAuditor[]
+  bidsectionId: string
+  uid: string
+  code: string
+  createTime: string | undefined
+  demand: string
+  file: iFileState
+  id: string
+  inspection: string
+  inspectionDetail: string
+  position: string
+  status: number
+  checkOrder: iModifiedOrder
+  auditHistory: iAuditHistoryState[][]
+  rectifiedInfo: iRectifiedState[]
+  latestAuditor: iLatestAuditorState
+}
+
+export interface iModifiedOrder {
+  name: string
+  create_time: string | undefined
+  opinion: string
+  status: number
+}
+export interface iFileState {
+  fileList: any[]
+  total: number
+}

+ 46 - 0
src/types/contract.d.ts

@@ -74,6 +74,7 @@ export interface iContractState {
   partyB: string
   partyBSigner: string
   price: string
+  paid: string
   projectId: string
   remarks: string
   returned: string
@@ -82,3 +83,48 @@ export interface iContractState {
   treeId: string
   updateTime: string
 }
+
+export interface iReceivableState {
+  accountId: string;
+  annexes: number;
+  bidsectionId: string;
+  contractsId: string;
+  createTime: string;
+  createUser: string;
+  fileCounts: number;
+  id: string;
+  page: number;
+  price: string;
+  projectId: string;
+  remarks: string;
+  time: string;
+  way: string;
+}
+
+
+export interface iEditableCellProps extends React.HTMLAttributes<HTMLElement> {
+  editing: boolean;
+  dataIndex: string;
+  title: any;
+  cellType: 'DatePicker' | 'text';
+  record: iReceivableState;
+  index: number;
+  children: React.ReactNode;
+}
+
+export interface iTemplateState {
+  attribution: string
+  children: iTemplateState[] | undefined
+  depth: number
+  id: number
+  isEnd: boolean
+  leaf: boolean
+  name: string
+  parentId: number
+  serial: string
+}
+export interface iShowTemplateState {
+  isShow: boolean
+  template: string
+  loading: boolean
+}

+ 1 - 1
src/types/rule.d.ts

@@ -1,6 +1,6 @@
 export interface iRulePayload {
   bidsectionId: string
-  type: 'safe_rule' | 'quality_rule' | 'contract_rule'
+  type: 'safe_rule' | 'quality_rule' | 'contract_return_rule' | 'contract_paid_rule'
   rule: ruleOption[]
 }
 

+ 8 - 4
src/types/safe.d.ts

@@ -23,16 +23,20 @@ export interface iAuditHistory {
 
 export interface iRectifiedState {
   name: string;
-  end_time: string;
+  create_time: string;
   opinion: string;
-  status: number;
 }
 
 export interface iLatestAuditorState {
   audit_id: string;
-  status: number;
+  audit_order: number;
+  bidsection_id: string;
+  data_id: string;
+  data_type: number;
+  id: string;
   progress: string;
-  id: string
+  project_id: string;
+  status: number;
 }
 
 export interface iAuditor {

+ 5 - 4
src/utils/consts.ts

@@ -9,15 +9,16 @@ export default {
   TOKEN_INVALID_CODE: [ 1 ], // 接口返回码如果是1 则表明token过期或无效 需要重新登录
   TOKEN_WHITE_LIST: [ '/api/login' ], // 不需要设置token的白名单
   RET_CODE: { SUCCESS: 0, FAIL: 1, TOKEN_UNDEFINED: 19, TOKEN_EXPIRED: 1 }, // 接口返回状态码
-  RETRY: { COUNT: 3, DELAY: 1000 }, // 请求重试次数/间隙
-  RULE: { SAFE: 'safeRule', QUALITY: 'qualityRule', CONTRACT: 'contractRule' }, // 编号规则弹窗常量
+  RETRY: { COUNT: 2, DELAY: 1000 }, // 请求重试次数/间隙
+  RULE: { SAFE: 'safeRule', QUALITY: 'qualityRule', CONTRACT_RETURN: 'contractReturnRule', CONTRACT_PAID: 'contractPaidRule' }, // 编号规则弹窗常量
   UPLOAD_WHITE: "(.json|.txt|.xls|.xlsx|.doc|.docx|.pdf|.ppt|.pptx|.png|.jpg|.jpeg|.gif|.bmp|.cad|.dwg|.zip|.rar|.7z)$"  , // 上传类型-白名单
   UPLOAD_LIMIT: 30, // 上传限制30MB
-  DATA_TYPE: { RETURN: 1, QUALITY: 2, SAFE: 3, CONTRACT: 4 }, // 附件类型
+  DATA_TYPE: { RETURN: 1, QUALITY: 2, SAFE: 3, CONTRACT_RETURN: 4, PAID: 5, CONTRACT_PAID: 6 }, // 附件类型
   PAGE_SIZE: 10, // 默认页数
   OSS_PATH: {
     REVIEW: 'https://file-upload.6jlzf.cn/',
     DOWNLOAD: 'https://measuresaas.oss-cn-shenzhen.aliyuncs.com/'
-  }
+  },
+  CONTRACT_TREE: { RETURN: 0, PAID: 1 } // 合同管理-树结构, 0 - 收入, 1- 支出
 }
 

+ 95 - 29
src/utils/util.ts

@@ -1,16 +1,16 @@
-import dayjs from "dayjs"
+import dayjs from 'dayjs'
 
 function getCookie(name: string) {
-  const prefix = name + "="
+  const prefix = name + '='
   const start = document.cookie.indexOf(prefix)
 
   if (start === -1) {
-      return null
+    return null
   }
 
-  let end = document.cookie.indexOf(";", start + prefix.length)
+  let end = document.cookie.indexOf(';', start + prefix.length)
   if (end === -1) {
-      end = document.cookie.length
+    end = document.cookie.length
   }
 
   const value = document.cookie.substring(start + prefix.length, end)
@@ -20,7 +20,7 @@ function getCookie(name: string) {
 // 本地存储封装
 const storage = {
   get(key: string) {
-    const val:string|null = localStorage.getItem(key)
+    const val: string | null = localStorage.getItem(key)
     if (val) {
       return JSON.parse(val)
     }
@@ -42,8 +42,8 @@ const throttle = (fn: Function, delay: number) => {
   // 定义上次触发时间
   let last: number = 0
   return (...args: any[]) => {
-    const now: number = + Date.now()
-    console.log("call", now, last, delay)
+    const now: number = +Date.now()
+    console.log('call', now, last, delay)
     if (now > last + delay) {
       last = now
       fn.apply(this, args)
@@ -66,14 +66,13 @@ const debounce = (fn: Function, delay: number) => {
   }
 }
 
-
 /**
  * 将子组件路径还原成带前缀的路径
  * @param parentPath - 父组件路径
  * @param pathOfTargetConfig - 用户希望访问的组件的在路由配置信息中填写的路径
  * @returns 拼接后的path
  */
-const combinationPath = (parentPath: string|undefined, pathOfTargetConfig: string): string => {
+const combinationPath = (parentPath: string | undefined, pathOfTargetConfig: string): string => {
   let combinedPath = !pathOfTargetConfig.startsWith('/') ? `/${pathOfTargetConfig}` : pathOfTargetConfig
   combinedPath = parentPath ? `${parentPath}${combinedPath}` : combinedPath
   return combinedPath
@@ -84,6 +83,7 @@ const combinationPath = (parentPath: string|undefined, pathOfTargetConfig: strin
  * @param format - 格式
  */
 const dayjsFormat = (date: dayjs.ConfigType, format: string = 'YYYY-MM-DD HH:mm:ss') => {
+  if (date === "0001-01-01 00:00:00") return ''
   return dayjs(date).format(format)
 }
 
@@ -92,10 +92,81 @@ const dayjsFormat = (date: dayjs.ConfigType, format: string = 'YYYY-MM-DD HH:mm:
  * @param len - 长度
  */
 const generatePsw = (len: number): string => {
-  const pasArr = [ 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','0','1','2','3','4','5','6','7','8','9','_','-','$','%','&','@','+','!' ]
-  let password = ""
-  for (let i=0; i<len; i++){
-    const x = Math.floor(Math.random()*pasArr.length)
+  const pasArr = [
+    'a',
+    'b',
+    'c',
+    'd',
+    'e',
+    'f',
+    'g',
+    'h',
+    'i',
+    'j',
+    'k',
+    'l',
+    'm',
+    'n',
+    'o',
+    'p',
+    'q',
+    'r',
+    's',
+    't',
+    'u',
+    'v',
+    'w',
+    'x',
+    'y',
+    'z',
+    'A',
+    'B',
+    'C',
+    'D',
+    'E',
+    'F',
+    'G',
+    'H',
+    'I',
+    'J',
+    'K',
+    'L',
+    'M',
+    'N',
+    'O',
+    'P',
+    'Q',
+    'R',
+    'S',
+    'T',
+    'U',
+    'V',
+    'W',
+    'X',
+    'Y',
+    'Z',
+    '0',
+    '1',
+    '2',
+    '3',
+    '4',
+    '5',
+    '6',
+    '7',
+    '8',
+    '9',
+    '_',
+    '-',
+    '$',
+    '%',
+    '&',
+    '@',
+    '+',
+    '!'
+  ]
+  let password = ''
+  for (let i = 0; i < len; i++) {
+    const x = Math.floor(Math.random() * pasArr.length)
     password += pasArr[x]
   }
   return password
@@ -110,30 +181,25 @@ const formatDate = (d: string) => {
   let minute: number | string = date.getMinutes()
   let scond: number | string = date.getSeconds()
   if (mon < 10) {
-      mon = '0' + mon.toString()
+    mon = '0' + mon.toString()
   }
   if (day < 10) {
-      day = '0' + day.toString()
+    day = '0' + day.toString()
   }
   if (hour < 10) {
-      hour = '0' + hour.toString()
+    hour = '0' + hour.toString()
   }
   if (minute < 10) {
-      minute = '0' + minute.toString()
+    minute = '0' + minute.toString()
   }
   if (scond < 10) {
-      scond = '0' + scond.toString()
+    scond = '0' + scond.toString()
   }
   return `<span>${year}</span><span>${mon}-${day}</span><span>${hour}:${minute}:${scond}</span>`
 }
-export {
-  getCookie,
-  storage,
-  throttle,
-  debounce,
-  combinationPath,
-  dayjsFormat,
-  generatePsw,
-  formatDate
-}
 
+// 数字千分位
+const formatMoney = (num: number) => {
+  return (Math.round(num) + '').replace(/\d{1,3}(?=(\d{3})+(\.\d*)?$)/g, '$&,') + '.00'
+}
+export { getCookie, storage, throttle, debounce, combinationPath, dayjsFormat, generatePsw, formatDate, formatMoney }