Ver código fonte

feat: 支出合同结构初始化

lanjianrong 4 anos atrás
pai
commit
b47d414255

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

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

@@ -1,6 +1,6 @@
 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'
@@ -52,7 +52,7 @@ 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="", signerTime = '', remarks = '' } = contractStore.contract
+        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 = [] }) => {
@@ -61,9 +61,9 @@ 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 ])

+ 16 - 31
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)
     }
   }
 
@@ -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,7 +261,7 @@ 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上传弹窗
@@ -285,7 +270,7 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
     const { code = -1 } = await apiSaveFileInfo(fileList, consts.DATA_TYPE.CONTRACT, 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, 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)
       }

+ 6 - 6
src/pages/Contract/Content/Income/index.tsx

@@ -3,7 +3,7 @@ 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'
@@ -60,10 +60,10 @@ export default function Income() {
     })
     const { code = -1, contract = {} } = await apiResfulContract(type, values)
     if (code === consts.RET_CODE.SUCCESS) {
-      contractStore.updateContract(contract)
-      contractStore.resetTree(tenderStore.bid)
+      contractReturnStore.updateContract(contract)
+      contractReturnStore.resetTree(tenderStore.bid)
       if (type === 'return') {
-        contractStore.changeUpdate('2')
+        contractReturnStore.changeUpdate('2')
       }
     }
     setModalObj({
@@ -82,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 })
       }

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

@@ -36,8 +36,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
 }
 
@@ -64,7 +64,7 @@ export async function apiUpdateName(id: string, bidsectionId: string, name: stri
 }
 
 /**
- * 合同增删改(包括添加回款)
+ * 合同增删改(包括添加已支付)
  * @param type - 操作类型
  * @param payload - 载荷
  */
@@ -72,11 +72,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 +84,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
-}

+ 18 - 17
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,18 +52,18 @@ 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 ])
@@ -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>

+ 25 - 39
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> : ''
@@ -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,7 +262,7 @@ 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上传弹窗
@@ -285,7 +271,7 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
     const { code = -1 } = await apiSaveFileInfo(fileList, consts.DATA_TYPE.CONTRACT, 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, 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%'

+ 11 - 12
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 })
       }
@@ -170,9 +169,9 @@ 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>

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

+ 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/utils/consts.ts

@@ -13,7 +13,7 @@ export default {
   RULE: { SAFE: 'safeRule', QUALITY: 'qualityRule', CONTRACT: 'contractRule' }, // 编号规则弹窗常量
   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: 4, PAID: 5 }, // 附件类型
   PAGE_SIZE: 10, // 默认页数
   OSS_PATH: {
     REVIEW: 'https://file-upload.6jlzf.cn/',

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