index.tsx 13 KB

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