index.tsx 13 KB

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