index.tsx 14 KB

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