index.tsx 14 KB

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