index.tsx 15 KB

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