index.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. import Authorization from '@/components/Authorization'
  2. import { ZhSubmitButton } from '@/components/Button'
  3. import OssUploadModal from '@/components/OssUpload'
  4. import { contractStore, tenderStore } from '@/store/mobx'
  5. import { ContractType } from '@/store/mobx/contract'
  6. import { ContractTree, iShowTemplateState, iTemplateState } from '@/types/contract'
  7. import { iFile } from '@/types/file'
  8. import { apiSaveFileInfo } from '@/utils/common/api'
  9. import { contractConsts } from '@/utils/common/constStatus'
  10. import { useTableExpand } from '@/hooks/tableHooks'
  11. import consts from '@/utils/consts'
  12. import { formatMoney } from '@/utils/util'
  13. import { Button, message, Radio, Table, Tabs } from 'antd'
  14. import Modal from 'antd/lib/modal/Modal'
  15. import { RadioChangeEvent } from 'antd/lib/radio'
  16. import { ColumnsType } from 'antd/lib/table'
  17. import { observer } from 'mobx-react'
  18. import React, { useEffect, useState } from 'react'
  19. import { apiGetContractWithDetail, apiUpdateContractName, apiUpdateSerial } from '../../api'
  20. import { apiContractTree, apiSetTemplate } from '../Modal/api'
  21. import Detail from '../Tabs/Detail'
  22. import File from '../Tabs/File'
  23. import Receivable from '../Tabs/Receivable'
  24. import { EditableCell, EditableRow } from './EditableTable'
  25. import styles from './index.module.scss'
  26. import './index.scss'
  27. type EditableTableProps = Parameters<typeof Table>[0];
  28. type ColumnTypes = Exclude<EditableTableProps['columns'], undefined>;
  29. interface iTableContentPorps {
  30. changeModalType: (type: string) => void
  31. row: ContractTree
  32. setRow: (record: ContractTree) => void
  33. history: any
  34. type: 'income' | 'expenditure'
  35. }
  36. const GCsheet: React.FC<iTableContentPorps> = ({ changeModalType, row, setRow, history, type }) => {
  37. const [ sectionTemplate, setSectionTemplate ] = useState<iShowTemplateState>({
  38. isShow: false,
  39. template: '',
  40. loading: false
  41. })
  42. const [ ids, setRowKeys ] = useTableExpand(contractStore.tree)
  43. const [ tempalte, setTempalte ] = useState<{ template1: iTemplateState, template2: iTemplateState }>({
  44. template1: {
  45. attribution: '',
  46. children: undefined,
  47. depth: 0,
  48. id: 0,
  49. isEnd: false,
  50. leaf: false,
  51. name: '',
  52. parentId: 0,
  53. serial: ''
  54. },
  55. template2: {
  56. attribution: '',
  57. children: undefined,
  58. depth: 0,
  59. id: 0,
  60. isEnd: false,
  61. leaf: false,
  62. name: '',
  63. parentId: 0,
  64. serial: ''
  65. }
  66. })
  67. // 阿里oss弹窗控制器
  68. const [ visible, setVisible ] = useState<boolean>(false)
  69. const { TabPane } = Tabs
  70. useEffect(() => {
  71. initData()
  72. }, [])
  73. const initData = async () => {
  74. const data = await apiContractTree(type, tenderStore.bid)
  75. if (data.code === consts.RET_CODE.SUCCESS) {
  76. if (data.isTemplate && data.isTemplate === 1) {
  77. setSectionTemplate({
  78. ...sectionTemplate,
  79. isShow: true
  80. })
  81. setTempalte({
  82. ...tempalte,
  83. template1: data.sectionTemplate1,
  84. template2: data.sectionTemplate2
  85. })
  86. } else {
  87. contractStore.updateTree(data.sectionTree.children)
  88. }
  89. }
  90. // 初始化时如果id存在说明只是table更新了,那么要将store里面的合同数据也一起更新,防止合同详情不是最新的数据
  91. if (row.id) {
  92. handleRowClick(row.id, row.bidsectionId)
  93. }
  94. }
  95. const handleUpdateContractName = async (payload: object) => {
  96. const { code = -1 } = await apiUpdateContractName(payload)
  97. if (code === consts.RET_CODE.SUCCESS) {
  98. contractStore.resetTree(type, tenderStore.tender.bidsectionId)
  99. }
  100. }
  101. const codeChange = async (value: string, row: ContractTree) => {
  102. const { code = -1 } = await apiUpdateSerial(row.id, row.bidsectionId, value, type === ContractType.INCOME ? 0 : 1)
  103. if (code === consts.RET_CODE.SUCCESS) {
  104. initData()
  105. }
  106. }
  107. const modalColumns: ColumnsType<iTemplateState> = [
  108. {
  109. title: '项目节',
  110. dataIndex: 'serial',
  111. width: '30%',
  112. // eslint-disable-next-line react/display-name
  113. render: (text: string, row: iTemplateState) => {
  114. const { attribution = '', serial = '' } = row
  115. return <span>{`${attribution}${serial}`}</span>
  116. }
  117. },
  118. {
  119. title: '名称',
  120. dataIndex: 'name',
  121. width: '70%'
  122. }
  123. ]
  124. const tableColumns: (ColumnTypes[number] & { editable?: boolean; dataIndex: string })[] = [
  125. {
  126. title: '编号',
  127. dataIndex: 'code',
  128. width: '20%',
  129. editable: true
  130. },
  131. {
  132. title: '项目名称',
  133. dataIndex: 'name',
  134. width: '20%',
  135. editable: true
  136. },
  137. {
  138. title: '合同名称',
  139. dataIndex: 'contractName',
  140. width: '15%'
  141. },
  142. {
  143. title: '合同编号',
  144. dataIndex: 'contractCode',
  145. width: '15%'
  146. },
  147. {
  148. title: '合同金额',
  149. dataIndex: 'contractPrice',
  150. align: 'right',
  151. width: '10%',
  152. // eslint-disable-next-line react/display-name
  153. render: (text: any) => <span>{formatMoney(text)}</span>
  154. },
  155. {
  156. title: type === ContractType.INCOME ? '回款金额' : '支付金额',
  157. dataIndex: type === ContractType.INCOME ? 'contractReturned' : 'contractsPaid',
  158. align: 'right',
  159. width: '10%',
  160. // eslint-disable-next-line react/display-name
  161. render: (text: any) => <span>{formatMoney(text)}</span>
  162. },
  163. {
  164. title: '状态',
  165. dataIndex: 'contractStatus',
  166. width: '10%',
  167. // eslint-disable-next-line react/display-name
  168. render: (_: any, record: any) => record.contractCode ? <span className={contractConsts[record.contractStatus].className}>{contractConsts[record.contractStatus].text}</span> : null
  169. }
  170. ]
  171. // modal 确认 - 回调
  172. const handleModalConfirm = async () => {
  173. setSectionTemplate({
  174. ...sectionTemplate,
  175. loading: true
  176. })
  177. if (!sectionTemplate.template) {
  178. setSectionTemplate({
  179. ...sectionTemplate,
  180. loading: false
  181. })
  182. return message.error('请选择项目节模板')
  183. }
  184. const { code = -1 } = await apiSetTemplate(sectionTemplate.template, tenderStore.tender.bidsectionId, type === 'income' ? 0 : 1)
  185. if (code === consts.RET_CODE.SUCCESS) {
  186. await initData()
  187. }
  188. setSectionTemplate({
  189. ...sectionTemplate,
  190. loading: false,
  191. isShow: false
  192. })
  193. }
  194. // modal 关闭 - 回调
  195. const handleModalCancel = () => {
  196. history.push('/console/contract/content/summary')
  197. }
  198. // 模板选择radio切换回调
  199. const handleRadioEvent = (e: RadioChangeEvent) => {
  200. if (e.target.checked) {
  201. setSectionTemplate({
  202. ...sectionTemplate,
  203. template: e.target.value
  204. })
  205. }
  206. }
  207. const onClickRow = (record: any) => {
  208. return {
  209. onClick() {
  210. handleRowClick(record.id, record.bidsectionId)
  211. }
  212. }
  213. }
  214. // 行点击回调
  215. const handleRowClick = async (id: string, bid: string) => {
  216. const { code = -1, section = {}, contract: newContract = {} } = await apiGetContractWithDetail(id, bid, type)
  217. if (code === consts.RET_CODE.SUCCESS) {
  218. setRow(section)
  219. contractStore.updateContract(newContract)
  220. }
  221. }
  222. const handleRowClass = (record: any) => {
  223. return record.id === row.id ? 'ant-table-row-selected editable-row' : ''
  224. }
  225. const tabOnClick = (key: string) => {
  226. contractStore.changeActiveKey(key)
  227. }
  228. // 阿里oss上传弹窗
  229. const onShow = (show: boolean) => setVisible(show)
  230. const onCreate = async (fileList: iFile[]) => {
  231. const { code = -1 } = await apiSaveFileInfo(fileList, type === ContractType.INCOME ? consts.DATA_TYPE.INCOME : consts.DATA_TYPE.EXPENDITURE, row.contractId)
  232. if (code === consts.RET_CODE.SUCCESS) {
  233. setVisible(false)
  234. contractStore.changeActiveKey('3')
  235. }
  236. }
  237. const components = {
  238. body: {
  239. row: EditableRow,
  240. cell: EditableCell
  241. }
  242. }
  243. const handleSave = (dataIndex: keyof ContractTree, value: string, row: ContractTree) => {
  244. if (dataIndex === 'code') {
  245. codeChange(value, row)
  246. } else {
  247. handleUpdateContractName({ id: row.id, bidsectionId: row.bidsectionId, treeType: type === ContractType.INCOME ? 0 : 1, name: value })
  248. }
  249. }
  250. const _tableColumns = tableColumns.map(col => {
  251. if (!col.editable) {
  252. return col
  253. }
  254. return {
  255. ...col,
  256. onCell: (record: ContractTree) => ({
  257. record,
  258. editable: col.editable,
  259. dataIndex: col.dataIndex,
  260. title: col.title,
  261. handleSave
  262. })
  263. }
  264. })
  265. return sectionTemplate.isShow ?
  266. <Modal
  267. visible={sectionTemplate.isShow}
  268. getContainer={false}
  269. maskClosable={false}
  270. title="选择合同项目节模板"
  271. okText="确定"
  272. confirmLoading={sectionTemplate.loading}
  273. cancelText="关闭"
  274. onCancel={handleModalCancel}
  275. closable={false}
  276. keyboard={false}
  277. onOk={() => handleModalConfirm()}
  278. width='70vw'
  279. >
  280. <div className={styles.modalWarnText}>默认项目节无法修改,可自行增加维护子节点</div>
  281. <div className={styles.modalTemplateContent}>
  282. <div className={[ styles.leftTemplate, sectionTemplate.template == '1' ? styles.active : '' ].join(' ')}>
  283. <div className="pi-pd-20">
  284. <Radio value="1" checked={sectionTemplate.template === '1'} onChange={(e: RadioChangeEvent) => handleRadioEvent(e)}><span className="pi-gray">项目节模板1</span></Radio>
  285. </div>
  286. <div className={styles.projectTable}>
  287. {
  288. tempalte.template1?.children && tempalte.template1?.children.length ?
  289. <Table
  290. dataSource={tempalte.template1?.children}
  291. columns={modalColumns}
  292. pagination={false}
  293. bordered
  294. scroll={{ y: '300px' }}
  295. rowKey={record => record.id}
  296. defaultExpandAllRows={true}
  297. /> : ''
  298. }
  299. </div>
  300. </div>
  301. <div className={[ styles.rightTemplate, sectionTemplate.template == '2' ? styles.active : '' ].join(' ')}>
  302. <div className="pi-pd-20 pi-gray">
  303. <Radio value="2" checked={sectionTemplate.template === '2'} onChange={(e: RadioChangeEvent) => handleRadioEvent(e)}><span className="pi-gray">项目节模板2</span></Radio>
  304. </div>
  305. <div className={styles.projectTable}>
  306. {
  307. tempalte.template2?.children && tempalte.template2?.children.length ?
  308. <Table
  309. dataSource={tempalte.template2?.children}
  310. columns={modalColumns}
  311. bordered
  312. pagination={false}
  313. scroll={{ y: '300px' }}
  314. rowKey={record => record.id}
  315. defaultExpandAllRows={true}
  316. />
  317. : ''
  318. }
  319. </div>
  320. </div>
  321. </div>
  322. </Modal>
  323. :
  324. <div className={styles.spreadContent}>
  325. <div className={styles.spreadSheets}>
  326. {
  327. contractStore.showTable ?
  328. <Table
  329. components={components}
  330. dataSource={contractStore.tree}
  331. columns={_tableColumns as ColumnTypes}
  332. bordered
  333. pagination={false}
  334. defaultExpandAllRows={true}
  335. onRow={onClickRow}
  336. rowClassName={handleRowClass}
  337. expandable={{ expandedRowKeys: ids, onExpand: (expanded: boolean, record: any) => setRowKeys(expanded, record as ContractTree) }}
  338. style={{ height: '100%', overflowY: 'scroll' }}
  339. />
  340. : ''
  341. }
  342. </div>
  343. <div className={styles.extraControl}>
  344. <Tabs
  345. type="card"
  346. size="small"
  347. defaultActiveKey="1"
  348. onTabClick={(key: string) => tabOnClick(key)}
  349. tabBarExtraContent={{
  350. right:
  351. <div className="pi-mg-right-5 pi-flex-row">
  352. {
  353. contractStore.contract.id && contractStore.contract.status === contractConsts.status.checking ?
  354. <>
  355. <Button type="primary" size="small" onClick={() => changeModalType('add')} >{type === ContractType.INCOME ? '添加回款' : '添加支付'}</Button>
  356. </>
  357. : null
  358. }
  359. {
  360. contractStore.contract.id && contractStore.contract.status !== contractConsts.status.closed ?
  361. <>
  362. <Authorization type="contract" auth="add">
  363. <Button type="primary" size="small" onClick={() => changeModalType('update')} className="pi-mg-left-5">编辑合同</Button>
  364. </Authorization>
  365. <Button type="primary" size="small" onClick={() => setVisible(true)} className="pi-mg-left-5">上传文件</Button>
  366. </>
  367. : null
  368. }
  369. {
  370. contractStore.contract.id && contractStore.contract.status === contractConsts.status.willClose ?
  371. <Button type="primary" size="small" danger className="pi-mg-left-5" onClick={() => changeModalType('close')}>关闭合同</Button>
  372. : null
  373. }
  374. {
  375. contractStore.contract.id && contractStore.contract.status === contractConsts.status.closed ?
  376. <ZhSubmitButton type="primary" size="small" danger className="pi-mg-left-5" onClick={() => changeModalType('unlock')}>解锁合同</ZhSubmitButton>
  377. : null
  378. }
  379. </div>
  380. }}>
  381. <TabPane key="1" tab="合同详情">
  382. <Detail contract={{ ...contractStore.contract }} type={type} />
  383. </TabPane>
  384. <TabPane key="2" tab={type === ContractType.INCOME ? '合同回款' : '合同支付'}>
  385. <Receivable updateTreeAndContract={initData} type={type}/>
  386. </TabPane>
  387. <TabPane key="3" tab="合同文件" >
  388. <File type={type}/>
  389. </TabPane>
  390. </Tabs>
  391. </div>
  392. <OssUploadModal
  393. visible={visible}
  394. onCancel={() => setVisible(false)}
  395. onCreate={onCreate}
  396. onShow={onShow}
  397. />
  398. </div>
  399. }
  400. export default observer(GCsheet)