|
@@ -0,0 +1,419 @@
|
|
|
+import { ZhSubmitButton } from '@/components/Button'
|
|
|
+import OssUploadModal from '@/components/OssUpload'
|
|
|
+import { contractStore, tenderStore } from '@/store/mobx'
|
|
|
+import { iIncomeTree } from '@/types/contract'
|
|
|
+import { iFile } from '@/types/file'
|
|
|
+import { apiSaveFileInfo } from '@/utils/common/api'
|
|
|
+import { contractConsts } from '@/utils/common/constStatus'
|
|
|
+import consts from '@/utils/consts'
|
|
|
+import { Button, Input, message, Radio, Table, Tabs } from 'antd'
|
|
|
+import Modal from 'antd/lib/modal/Modal'
|
|
|
+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 Detail from '../Tabs/Detail'
|
|
|
+import File from '../Tabs/File'
|
|
|
+import Receivable from '../Tabs/Receivable'
|
|
|
+import styles from './index.module.scss'
|
|
|
+interface iTableContentPorps {
|
|
|
+ modalHandler: (type: string) => void
|
|
|
+ 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>({
|
|
|
+ isShow: false,
|
|
|
+ template: '',
|
|
|
+ loading: false
|
|
|
+ })
|
|
|
+ const [ tempalte, setTempalte ] = useState<{template1: iTemplateState, template2: iTemplateState}>({
|
|
|
+ template1: {
|
|
|
+ attribution: '',
|
|
|
+ children: undefined,
|
|
|
+ depth: 0,
|
|
|
+ id: 0,
|
|
|
+ isEnd: false,
|
|
|
+ leaf: false,
|
|
|
+ name: '',
|
|
|
+ parentId: 0,
|
|
|
+ serial: ''
|
|
|
+ },
|
|
|
+ template2: {
|
|
|
+ attribution: '',
|
|
|
+ children: undefined,
|
|
|
+ depth: 0,
|
|
|
+ id: 0,
|
|
|
+ isEnd: false,
|
|
|
+ leaf: false,
|
|
|
+ name: '',
|
|
|
+ parentId: 0,
|
|
|
+ serial: ''
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 阿里oss弹窗控制器
|
|
|
+ const [ visible, setVisible ] = useState<boolean>(false)
|
|
|
+
|
|
|
+ const { TabPane } = Tabs
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ initHandler()
|
|
|
+ }, [])
|
|
|
+ const initHandler = async () => {
|
|
|
+ const data = await apiContractIncome(tenderStore.bid)
|
|
|
+ if (data.code === consts.RET_CODE.SUCCESS) {
|
|
|
+ if (data.isTemplate && data.isTemplate === 1) {
|
|
|
+ setSectionTemplate({
|
|
|
+ ...sectionTemplate,
|
|
|
+ isShow: true
|
|
|
+ })
|
|
|
+ setTempalte({
|
|
|
+ ...tempalte,
|
|
|
+ template1: data.sectionTemplate1,
|
|
|
+ template2: data.sectionTemplate2
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ contractStore.updateTree(data.sectionTree.children)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ interface iLabelHandlerProps {
|
|
|
+ id: string
|
|
|
+ bidsectionId: string
|
|
|
+ name?: string
|
|
|
+ }
|
|
|
+
|
|
|
+ const newLabelHandler = async (type: string, payload: iLabelHandlerProps) => {
|
|
|
+ let RET_CODE: number = -1
|
|
|
+ if (type === 'create') {
|
|
|
+ payload.name = inputEl.current?.state.value
|
|
|
+ const { code = -1 } = await apiResfulContractTree('add', payload)
|
|
|
+ RET_CODE = code
|
|
|
+ }
|
|
|
+ if (type === 'edit') {
|
|
|
+ const name = inputEl.current?.state.value
|
|
|
+ const { code = -1 } = await apiUpdateName(payload.id, payload.bidsectionId, name)
|
|
|
+ RET_CODE = code
|
|
|
+ }
|
|
|
+ if (RET_CODE === consts.RET_CODE.SUCCESS) {
|
|
|
+ contractStore.resetTree(tenderStore.tender.bidsectionId)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const codeChange = async (row: iIncomeTree, value: string) => {
|
|
|
+ const { code = -1 } = await apiUpdateSerial(row.id, row.bidsectionId, value)
|
|
|
+ if (code === consts.RET_CODE.SUCCESS) {
|
|
|
+ initHandler()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const inputEl = useRef<Input>(null)
|
|
|
+ const modalColumns: ColumnsType<iTemplateState> = [
|
|
|
+ {
|
|
|
+ title: '项目节',
|
|
|
+ dataIndex: 'serial',
|
|
|
+ width: '30%',
|
|
|
+ // eslint-disable-next-line react/display-name
|
|
|
+ render: (text: string, row: iTemplateState) => {
|
|
|
+ const { attribution = '', serial = '' } = row
|
|
|
+ return <span>{`${attribution}${serial}`}</span>
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '名称',
|
|
|
+ dataIndex: 'name',
|
|
|
+ width: '70%'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+
|
|
|
+ const TableColumns: ColumnsType<iIncomeTree> = [
|
|
|
+ {
|
|
|
+ title: '编号',
|
|
|
+ dataIndex: 'code',
|
|
|
+ width: '15%',
|
|
|
+ // eslint-disable-next-line react/display-name
|
|
|
+ render: (text: string, row: iIncomeTree) => {
|
|
|
+ if (row.isEdit) {
|
|
|
+ return (
|
|
|
+ <Input
|
|
|
+ defaultValue={row.serial}
|
|
|
+ addonBefore={row.attribution}
|
|
|
+ size="small"
|
|
|
+ style={{ width: 80 }}
|
|
|
+ onPressEnter={(e: KeyboardEvent<HTMLInputElement>) => codeChange(row, e.currentTarget.value)}
|
|
|
+ />)
|
|
|
+ } else {
|
|
|
+ return <span>{row.code}</span>
|
|
|
+ }
|
|
|
+ // return <span>{row.code}</span>
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '项目名称',
|
|
|
+ dataIndex: 'name',
|
|
|
+ render: (text:any, record: iIncomeTree) => {
|
|
|
+ if (record.isEdit || record.isNew) {
|
|
|
+ const type = record.isEdit ? 'edit' : 'create'
|
|
|
+ return (
|
|
|
+ <Input
|
|
|
+ defaultValue={record.name}
|
|
|
+ size="small"
|
|
|
+ type="text"
|
|
|
+ ref={inputEl}
|
|
|
+ onPressEnter={() => newLabelHandler(type, { id: type === 'edit' ? record.id : record.parentId, bidsectionId: record.bidsectionId })}
|
|
|
+ onBlur={() => newLabelHandler(type, { id: type === 'edit' ? record.id : record.parentId, bidsectionId: record.bidsectionId })}
|
|
|
+ />)
|
|
|
+ } else {
|
|
|
+ return <span>{text}</span>
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '合同名称',
|
|
|
+ dataIndex: 'contractName'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '合同编号',
|
|
|
+ dataIndex: 'contractCode'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '合同金额',
|
|
|
+ dataIndex: 'contractPrice',
|
|
|
+ align: 'right',
|
|
|
+ // eslint-disable-next-line react/display-name
|
|
|
+ render: (text:any, record: iIncomeTree) => record.contractCode ? <span>{text}</span> : ''
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '回款金额',
|
|
|
+ dataIndex: 'contractReturned',
|
|
|
+ align: 'right',
|
|
|
+ // eslint-disable-next-line react/display-name
|
|
|
+ render: (text:any, record: iIncomeTree) => record.contractCode ? <span>{text}</span> : ''
|
|
|
+ },
|
|
|
+ {
|
|
|
+ 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> : ''
|
|
|
+ }
|
|
|
+
|
|
|
+ ]
|
|
|
+
|
|
|
+ // modal 确认 - 回调
|
|
|
+ const handleModalConfirm = async () => {
|
|
|
+ setSectionTemplate({
|
|
|
+ ...sectionTemplate,
|
|
|
+ loading: true
|
|
|
+ })
|
|
|
+ if (!sectionTemplate.template) {
|
|
|
+ setSectionTemplate({
|
|
|
+ ...sectionTemplate,
|
|
|
+ loading: false
|
|
|
+ })
|
|
|
+ return message.error('请选择项目节模板!')
|
|
|
+ }
|
|
|
+ const { code = -1 } = await apiSetTemplate(sectionTemplate.template, tenderStore.tender.bidsectionId)
|
|
|
+ if (code === consts.RET_CODE.SUCCESS) {
|
|
|
+ await initHandler()
|
|
|
+ }
|
|
|
+ setSectionTemplate({
|
|
|
+ ...sectionTemplate,
|
|
|
+ loading: false,
|
|
|
+ isShow: false
|
|
|
+ })
|
|
|
+ }
|
|
|
+ // 模板选择radio切换回调
|
|
|
+ const handleRadioEvent = (e: RadioChangeEvent) => {
|
|
|
+ if (e.target.checked) {
|
|
|
+ setSectionTemplate({
|
|
|
+ ...sectionTemplate,
|
|
|
+ template: e.target.value
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const onClickRow = (record: iIncomeTree) => {
|
|
|
+ return {
|
|
|
+ onClick() {
|
|
|
+ rowClickHandler(record.id, record.bidsectionId, record.isEdit, record.isNew)
|
|
|
+ },
|
|
|
+ onDoubleClick() {
|
|
|
+ contractStore.rowChange(row.id)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 行点击回调
|
|
|
+ const rowClickHandler = async (id: string, bid: string, isEdit?: boolean, isNew?: boolean) => {
|
|
|
+ if (!isEdit && !isNew) {
|
|
|
+ const { code = -1, section = {}, contract: newContract = {} } = await apiGetIncome(id, bid)
|
|
|
+ if (code === consts.RET_CODE.SUCCESS) {
|
|
|
+ setRow(section)
|
|
|
+ contractStore.updateContract(newContract)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const handleRowClass = (record: iIncomeTree) => {
|
|
|
+ return record.id === row.id ? 'ant-table-row-selected' : ''
|
|
|
+ }
|
|
|
+ const tabOnClick = (key: string) => {
|
|
|
+ contractStore.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)
|
|
|
+ if (code === consts.RET_CODE.SUCCESS) {
|
|
|
+ setVisible(false)
|
|
|
+ contractStore.changeUpdate('3')
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return sectionTemplate.isShow ?
|
|
|
+ <Modal
|
|
|
+ visible={sectionTemplate.isShow}
|
|
|
+ maskClosable={false}
|
|
|
+ title="选择合同项目节模板"
|
|
|
+ okText="确定"
|
|
|
+ confirmLoading={sectionTemplate.loading}
|
|
|
+ cancelText="关闭"
|
|
|
+ closable={false}
|
|
|
+ keyboard={false}
|
|
|
+ onOk={() => handleModalConfirm()}
|
|
|
+ width='70vw'
|
|
|
+ >
|
|
|
+ <div className={styles.modalWarnText}>默认项目节无法修改,可自行增加维护子节点</div>
|
|
|
+ <div className={styles.modalTemplateContent}>
|
|
|
+ <div className={styles.leftTemplate}>
|
|
|
+ <div className="pi-pd-20">
|
|
|
+ <Radio value="1" checked={sectionTemplate.template === '1'} onChange={(e: RadioChangeEvent) => handleRadioEvent(e)}><span className="pi-gray">项目节模板1</span></Radio>
|
|
|
+ </div>
|
|
|
+ <div className={styles.projectTable}>
|
|
|
+ {
|
|
|
+ tempalte.template1?.children && tempalte.template1?.children.length?
|
|
|
+ <Table
|
|
|
+ dataSource={tempalte.template1?.children}
|
|
|
+ columns={modalColumns}
|
|
|
+ pagination={false}
|
|
|
+ bordered
|
|
|
+ scroll={{ y: '300px' }}
|
|
|
+ rowKey={record => record.id}
|
|
|
+ defaultExpandAllRows={true}
|
|
|
+ >
|
|
|
+ </Table> : ''
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+ <div className={styles.rightTemplate}>
|
|
|
+ <div className="pi-pd-20 pi-gray">
|
|
|
+ <Radio value="2" checked={sectionTemplate.template === '2'} onChange={(e: RadioChangeEvent) => handleRadioEvent(e)}><span className="pi-gray">项目节模板2</span></Radio>
|
|
|
+ </div>
|
|
|
+ <div className={styles.projectTable}>
|
|
|
+ {
|
|
|
+ tempalte.template2?.children && tempalte.template2?.children.length?
|
|
|
+ <Table
|
|
|
+ dataSource={tempalte.template2?.children}
|
|
|
+ columns={modalColumns}
|
|
|
+ bordered
|
|
|
+ pagination={false}
|
|
|
+ scroll={{ y: '300px' }}
|
|
|
+ rowKey={record => record.id}
|
|
|
+ defaultExpandAllRows={true}
|
|
|
+ />
|
|
|
+ : ''
|
|
|
+ }
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Modal>
|
|
|
+ :
|
|
|
+ <div className={styles.spreadContent}>
|
|
|
+ <div className={styles.spreadSheets}>
|
|
|
+ {
|
|
|
+ contractStore.showTable ?
|
|
|
+ <Table<iIncomeTree>
|
|
|
+ dataSource={contractStore.tree}
|
|
|
+ columns={TableColumns}
|
|
|
+ bordered
|
|
|
+ pagination={false}
|
|
|
+ rowKey={record => record.id}
|
|
|
+ defaultExpandAllRows={true}
|
|
|
+ onRow={onClickRow}
|
|
|
+ rowClassName={handleRowClass}
|
|
|
+ style={{ height: '100%', overflowY: 'scroll' }}
|
|
|
+ />
|
|
|
+ : ''
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ <div className={styles.extraControl}>
|
|
|
+ <Tabs
|
|
|
+ type="card"
|
|
|
+ size="small"
|
|
|
+ defaultActiveKey="1"
|
|
|
+ onTabClick={(key: string) => tabOnClick(key)}
|
|
|
+ tabBarExtraContent={{ right:
|
|
|
+ <div className="pi-mg-right-5 pi-flex-row">
|
|
|
+ {
|
|
|
+ contractStore.contract.id && contractStore.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={() => setVisible(true)}>上传文件</Button>
|
|
|
+ </>
|
|
|
+ : ''
|
|
|
+ }
|
|
|
+ {
|
|
|
+ contractStore.contract.id && contractStore.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 ?
|
|
|
+ <ZhSubmitButton type="primary" size="small" danger className="pi-mg-right-3" onClick={() => modalHandler('unlock')}>解锁合同</ZhSubmitButton>
|
|
|
+ : ''
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }}>
|
|
|
+ <TabPane key="1" tab="合同详情">
|
|
|
+ <Detail {...contractStore.contract}></Detail>
|
|
|
+ </TabPane>
|
|
|
+ <TabPane key="2" tab="合同回款">
|
|
|
+ <Receivable></Receivable>
|
|
|
+ </TabPane>
|
|
|
+ <TabPane key="3" tab="合同文件">
|
|
|
+ <File></File>
|
|
|
+ </TabPane>
|
|
|
+ </Tabs>
|
|
|
+ </div>
|
|
|
+ <OssUploadModal
|
|
|
+ visible={visible}
|
|
|
+ onCancel={() => setVisible(false)}
|
|
|
+ onCreate={onCreate}
|
|
|
+ onShow={onShow}
|
|
|
+ ></OssUploadModal>
|
|
|
+ </div>
|
|
|
+}
|
|
|
+
|
|
|
+export default observer(GCsheet)
|