index.tsx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. import AuditContent from '@/components/AuditContent'
  2. import { ZhAuditBackButton, ZhCloseButton, ZhSubmitButton, ZhUploadButton } from '@/components/Button'
  3. import DatePicker from '@/components/DatePicker'
  4. import Header from '@/components/Header'
  5. import Slot from '@/components/Header/slot'
  6. import OssUploadModal from '@/components/OssUpload'
  7. import SvgIcon from '@/components/SvgIcon'
  8. import { userStore } from '@/store/mobx'
  9. import { iDetailState } from '@/types/auditDetail'
  10. import { iFile } from '@/types/file'
  11. import { iAuditor } from '@/types/safe'
  12. import { iUserInfo } from '@/types/setting'
  13. import { apiDelFile, apiGetFileList, apiSaveFileInfo } from '@/utils/common/api'
  14. import { auditProgress } from '@/utils/common/constStatus'
  15. import consts from '@/utils/consts'
  16. import { dayjsFormat } from '@/utils/util'
  17. import { Button, Input, message, Pagination, Tooltip } from 'antd'
  18. import locale from 'antd/es/date-picker/locale/zh_CN'
  19. import dayjs from 'dayjs'
  20. import React, { useEffect, useState, useMemo } from 'react'
  21. import { RouteComponentProps } from 'react-router'
  22. import { apiGetSafeDetail, apiResfulSafeAudit } from './api'
  23. import AuditModal from '@/pages/Quality/Content/Info/Detail/components/Modal'
  24. import styles from './index.module.scss'
  25. import Authorization from '@/components/Authorization'
  26. const { TextArea } = Input
  27. interface iModalObj {
  28. ossModal: boolean
  29. auditModal: boolean
  30. auditType: string
  31. curPage: number
  32. loading: boolean
  33. }
  34. const Detail: React.FC<RouteComponentProps> = props => {
  35. const { id = '' } = props.location.state as any
  36. const [ modalObj, setModalObj ] = useState<iModalObj>({
  37. ossModal: false,
  38. auditModal: false,
  39. auditType: '',
  40. loading: false,
  41. curPage: 1
  42. })
  43. const [ detail, setDetail ] = useState<iDetailState>({
  44. auditName: '',
  45. uid: '',
  46. auditors: [],
  47. bidsectionId: '',
  48. code: '',
  49. createTime: new Date().toDateString(),
  50. demand: '',
  51. file: { fileList: [], total: 0 },
  52. id: '',
  53. inspection: '',
  54. inspectionDetail: '',
  55. position: '',
  56. status: 0,
  57. checkOrder: { // 整改单
  58. name: '',
  59. status: 0,
  60. opinion: '',
  61. create_time: new Date().toDateString()
  62. },
  63. auditHistory: [],
  64. rectifiedInfo: [],
  65. latestAuditor: {
  66. auditId: '',
  67. auditOrder: 0,
  68. bidsectionId: '',
  69. dataId: '',
  70. dataType: 0,
  71. id: '',
  72. progress: '',
  73. projectId: '',
  74. status: 0
  75. },
  76. times: 0
  77. })
  78. const isEdited = useMemo(() => {
  79. return !!detail.status
  80. }, [ detail.status ])
  81. useEffect(() => {
  82. initData()
  83. }, [])
  84. const initData = async () => {
  85. const { code = -1, data = {} } = await apiGetSafeDetail(id)
  86. if (code === consts.RET_CODE.SUCCESS) {
  87. setDetail({ ...detail, ...data })
  88. }
  89. if (!userStore.groupList.length) {
  90. userStore.getGroupList()
  91. }
  92. }
  93. const onCreate = async (fileList: iFile[]) => {
  94. const { code = -1 } = await apiSaveFileInfo(fileList, consts.DATA_TYPE.SAFE, detail.id)
  95. if (code === consts.RET_CODE.SUCCESS) {
  96. const newFiles = detail.file.fileList.concat(
  97. fileList.map(file => {
  98. return { ...file, accountName: userStore.userInfo.name }
  99. })
  100. )
  101. setDetail({ ...detail, file: { ...detail.file, total: newFiles.length } })
  102. await fileListChange(modalObj.curPage)
  103. }
  104. }
  105. const onOssModalShow = (show: boolean) => setModalObj({ ...modalObj, ossModal: show })
  106. const fileListChange = async (pageNo: number = 1, pageSize: number = 10) => {
  107. const { code = -1, data, total } = await apiGetFileList(consts.DATA_TYPE.SAFE, detail.id, pageNo, pageSize)
  108. if (code === consts.RET_CODE.SUCCESS) {
  109. setModalObj({ ...modalObj, curPage: pageNo, ossModal: false })
  110. setDetail({ ...detail, file: { ...detail.file, fileList: data, total } })
  111. }
  112. }
  113. const delFile = async (id: string, isLast: boolean) => {
  114. const { code = -1 } = await apiDelFile(id)
  115. if (code === consts.RET_CODE.SUCCESS) {
  116. await fileListChange(isLast ? modalObj.curPage - 1 : modalObj.curPage)
  117. isLast && (setModalObj({ ...modalObj, curPage: modalObj.curPage - 1 }))
  118. }
  119. }
  120. const addAuditor = (type: string, user: iUserInfo) => {
  121. if (detail.auditors.find(item => item.progress === (type === 'check' ? '0' : '2') && item.auditId === user.id)) {
  122. return message.error('该审批组下已存在该审批人,请勿重复添加')
  123. }
  124. if (type === 'check') {
  125. const newAuditors = detail.auditors
  126. const len = detail.auditors.filter((item: iAuditor) => item.progress === '0').length
  127. newAuditors.push({
  128. id: '',
  129. mobile: '',
  130. audit_id: user.id,
  131. audit_order: len + 1,
  132. position: user.position,
  133. progress: '0',
  134. name: user.name,
  135. accountGroup: user.accountGroup,
  136. company: user.company,
  137. status: 0
  138. })
  139. setDetail({ ...detail, auditors: newAuditors })
  140. } else {
  141. const newAuditors = detail.auditors
  142. const len = detail.auditors.filter((item: iAuditor) => item.progress === '2').length
  143. newAuditors.push({
  144. id: '',
  145. audit_id: user.id,
  146. mobile: '',
  147. auditOrder: len + 1,
  148. position: user.position,
  149. progress: '2',
  150. name: user.name,
  151. accountGroup: user.accountGroup,
  152. company: user.company,
  153. status: 0
  154. })
  155. setDetail({ ...detail, auditors: newAuditors })
  156. }
  157. }
  158. const delAuditor = (id: string, progress: string) => {
  159. const newAuditors = detail.auditors.filter(item => item.progress !== progress)
  160. const auditor = detail.auditors.find(item => item.progress === progress && item.audit_id !== id)
  161. if (auditor) {
  162. newAuditors.push(auditor)
  163. }
  164. setDetail({ ...detail, auditors: newAuditors })
  165. }
  166. const btnClick = (type: string) => {
  167. if (type === 'start') {
  168. if (!detail.inspectionDetail || !detail.demand) {
  169. return message.error('现场检查情况或处理要求措施不能为空')
  170. }
  171. }
  172. if (type === 'pass' && detail.latestAuditor.progress === '1' && !detail.checkOrder.opinion) {
  173. return message.error('请填写整改单')
  174. }
  175. setModalObj({ ...modalObj, auditType: type, auditModal: true })
  176. }
  177. const onModalConfirm = (values?: object) => {
  178. let payload: any = { safe_id: detail.id, bidsection_id: detail.bidsectionId, ...values }
  179. if (modalObj.auditType === 'start') {
  180. payload.inspection = detail.inspection
  181. payload.inspectionDetail = detail.inspectionDetail
  182. payload.demand = detail.demand
  183. payload.createTime = detail.createTime
  184. payload.auditors = detail.auditors.filter(item => item.progress === '0').map(item => item.audit_id)
  185. payload.reAuditors = detail.auditors.filter(item => item.progress === '2').map(item => item.audit_id)
  186. payload.times = detail.times
  187. if (!payload.auditors.length || !payload.reAuditors.length) {
  188. return message.error('审批人或复查人不能为空')
  189. }
  190. }
  191. if (modalObj.auditType === 'delete') {
  192. payload = { id: detail.id }
  193. }
  194. if (modalObj.auditType === 'pass' || modalObj.auditType === 'back') {
  195. payload.id = detail.latestAuditor.id
  196. if (detail.latestAuditor.progress === '1') {
  197. // if (!detail.checkOrder.opinion) {
  198. // return message.error("请填写整改单")
  199. // } else {
  200. // }
  201. payload.rectifiedInfo = detail.checkOrder.opinion
  202. }
  203. }
  204. if (modalObj.auditType === 'close') {
  205. payload.id = detail.latestAuditor.id
  206. }
  207. apiResful(modalObj.auditType, payload)
  208. }
  209. const apiResful = async (type: string, payload: any) => {
  210. setModalObj({ ...modalObj, loading: true })
  211. const { code } = await apiResfulSafeAudit(type, payload)
  212. if (code === consts.RET_CODE.SUCCESS) {
  213. setModalObj({ ...modalObj, auditModal: false, loading: false })
  214. if (type === 'delete') {
  215. props.history.goBack()
  216. } else {
  217. initData()
  218. }
  219. } else {
  220. setModalObj({ ...modalObj, loading: false })
  221. }
  222. }
  223. const renderHeaderBtn = (status: number) => {
  224. if (!detail.latestAuditor.auditId && userStore.userInfo.id !== detail.uid) return null
  225. if (detail.latestAuditor.auditId && userStore.userInfo.id !== detail.latestAuditor.auditId) return null
  226. if (!status) {
  227. return (
  228. <div className="pi-flex-row pi-align-center">
  229. <Authorization type="safe" auth="delete">
  230. <ZhCloseButton size="small" onClick={() => btnClick('delete')}>删除巡检</ZhCloseButton>
  231. </Authorization>
  232. <ZhSubmitButton size="small" className="pi-mg-left-5" onClick={() => btnClick('start')}>提交审批</ZhSubmitButton>
  233. </div>
  234. )
  235. } else if (status === auditProgress.checking || status === auditProgress.checkNo) {
  236. return (
  237. <div className="pi-flex-row pi-align-center">
  238. <Button type="primary" danger size="small" onClick={() => btnClick('close')}>关闭</Button>
  239. <ZhAuditBackButton size="small" className="pi-mg-left-5" onClick={() => btnClick('back')}>审批退回</ZhAuditBackButton>
  240. <ZhSubmitButton size="small" className="pi-mg-left-5" onClick={() => btnClick('pass')}>审批通过</ZhSubmitButton>
  241. </div>
  242. )
  243. } else if (status === auditProgress.checked) {
  244. return (
  245. <div className="pi-flex-row pi-align-center">
  246. <ZhAuditBackButton size="small" onClick={() => btnClick('back')}>审批退回</ZhAuditBackButton>
  247. <ZhSubmitButton size="small" className="pi-mg-left-5" onClick={() => btnClick('pass')}>整改完成</ZhSubmitButton>
  248. </div>
  249. )
  250. }
  251. }
  252. const modalProps = { visible: modalObj.auditModal, loading: modalObj.loading, type: modalObj.auditType }
  253. const auditData = { auditors: detail.auditors, auditHistory: detail.auditHistory, status: detail.status, uName: detail.auditName, uid: detail.uid }
  254. return (
  255. <div className="wrap-contaniner">
  256. <Header title="安全巡检">
  257. <Slot position="right">
  258. {renderHeaderBtn(detail.status)}
  259. </Slot>
  260. </Header>
  261. <div className={styles.detailContainer}>
  262. <div className={styles.content}>
  263. <h4 className={styles.header}>{detail.code}</h4>
  264. <table className="pi-table pi-bordered">
  265. <thead>
  266. <tr>
  267. <th colSpan={2} className="pi-text-center">
  268. 安全巡检单
  269. </th>
  270. </tr>
  271. </thead>
  272. <tbody>
  273. <tr>
  274. <th style={{ width: '150px' }}>检查项目</th>
  275. <td>
  276. {
  277. isEdited ? <span>{detail.inspection}</span> : <TextArea value={detail.inspection} onChange={(e) => { setDetail({ ...detail, inspection: e.currentTarget.value }) }} />
  278. }
  279. </td>
  280. </tr>
  281. <tr>
  282. <th style={{ width: '150px' }}>现场检查情况</th>
  283. <td>
  284. {
  285. isEdited ? <span>{detail.inspectionDetail}</span> : <TextArea value={detail.inspectionDetail} onChange={(e) => { setDetail({ ...detail, inspectionDetail: e.currentTarget.value }) }} />
  286. }
  287. </td>
  288. </tr>
  289. <tr>
  290. <th style={{ width: '150px' }}>处理要求及措施</th>
  291. <td>
  292. {
  293. isEdited ? <span>{detail.demand}</span> : <TextArea value={detail.demand} onChange={(e) => { setDetail({ ...detail, demand: e.currentTarget.value }) }} />
  294. }
  295. </td>
  296. </tr>
  297. <tr>
  298. <th style={{ width: '150px' }}>检查日期</th>
  299. <td>
  300. {
  301. isEdited ? <span>{detail.createTime && dayjsFormat(detail.createTime, 'YYYY-MM-DD')}</span> :
  302. <DatePicker
  303. size="small"
  304. locale={locale}
  305. allowClear={false}
  306. value={dayjs(detail.createTime)}
  307. onChange={value => setDetail({ ...detail, createTime: value?.format() })} />
  308. }
  309. </td>
  310. </tr>
  311. <tr>
  312. <th style={{ width: '150px' }}>质检员</th>
  313. <td>{detail.auditName}</td>
  314. </tr>
  315. </tbody>
  316. </table>
  317. {
  318. detail.status === auditProgress.checked && detail.latestAuditor.auditId === userStore.userInfo.id ?
  319. <table className="pi-table pi-bordered">
  320. <thead>
  321. <tr>
  322. <th colSpan={2} className="pi-text-center">
  323. 整改单
  324. </th>
  325. </tr>
  326. </thead>
  327. <tbody>
  328. <tr>
  329. <th style={{ width: '150px' }}>整改情况</th>
  330. <td>
  331. <TextArea value={detail.checkOrder.opinion} onChange={(e) => setDetail({ ...detail, checkOrder: { ...detail.checkOrder, opinion: e.currentTarget.value } })} />
  332. </td>
  333. </tr>
  334. <tr>
  335. <th style={{ width: '150px' }}>整改日期</th>
  336. <td>
  337. <DatePicker
  338. size="small"
  339. locale={locale}
  340. allowClear={false}
  341. value={dayjs(detail.checkOrder.create_time)}
  342. onChange={value => setDetail({ ...detail, checkOrder: { ...detail.checkOrder, create_time: value?.format() } })} />
  343. </td>
  344. </tr>
  345. <tr>
  346. <th style={{ width: '150px' }}>整改人</th>
  347. <td>{detail.auditors.find(item => item.progress === '1')?.name}</td>
  348. </tr>
  349. </tbody>
  350. </table>
  351. : detail.rectifiedInfo.map(item => (
  352. <table className="pi-table pi-bordered" key={item.create_time}>
  353. <thead>
  354. <tr>
  355. <th colSpan={2} className="pi-text-center">
  356. 整改单
  357. </th>
  358. </tr>
  359. </thead>
  360. <tbody>
  361. <tr>
  362. <th style={{ width: '150px' }}>整改情况</th>
  363. <td>{item.opinion}</td>
  364. </tr>
  365. <tr>
  366. <th style={{ width: '150px' }}>整改日期</th>
  367. <td>{dayjsFormat(item.create_time, 'YYYY-MM-DD')}</td>
  368. </tr>
  369. <tr>
  370. <th style={{ width: '150px' }}>整改人</th>
  371. <td>{item.name}</td>
  372. </tr>
  373. </tbody>
  374. </table>
  375. ))
  376. }
  377. <table className="pi-table pi-bordered mt-3">
  378. <thead>
  379. <tr>
  380. <th />
  381. <th className="pi-text-center">附件</th>
  382. <th className="pi-text-center">上传者</th>
  383. <th className="pi-text-center" style={{ width: 200 }}>
  384. 上传时间
  385. </th>
  386. <th className="pi-text-center">操作</th>
  387. </tr>
  388. </thead>
  389. <tbody>
  390. <tr>
  391. <td colSpan={5}>
  392. <ZhUploadButton size="small" icon={<SvgIcon type="xxh-cloud-upload" />} onClick={() => setModalObj({ ...modalObj, ossModal: true })}>
  393. 上传附件
  394. </ZhUploadButton>
  395. </td>
  396. </tr>
  397. {detail.file.fileList?.map((file, idx) => (
  398. <tr key={idx}>
  399. <td className="pi-width-70">{idx + 1}</td>
  400. <td style={{ width: 383, maxWidth: 383, overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }}>
  401. <a href={consts.OSS_PATH.REVIEW + file.filepath} target="_blank" rel="noopener noreferrer">
  402. {file.filename}
  403. </a>
  404. </td>
  405. <td className="pi-text-center pi-width-100">{file.accountName}</td>
  406. <td className="pi-text-center">{dayjsFormat(file.createTime)}</td>
  407. <td className="pi-text-center pi-width-90">
  408. <Tooltip title="移除">
  409. <Button size="small" type="text" icon={<SvgIcon type="xxh-times-circle1" />} style={{ color: '#df3f45' }} onClick={() => delFile(file.id, !idx)} />
  410. </Tooltip>
  411. </td>
  412. </tr>
  413. ))}
  414. {detail.file.total > consts.PAGE_SIZE ? (
  415. <tr>
  416. <td colSpan={5} className="pi-text-right">
  417. <Pagination
  418. current={modalObj.curPage}
  419. size="small"
  420. pageSize={consts.PAGE_SIZE}
  421. hideOnSinglePage={true}
  422. total={detail.file.total}
  423. onChange={(page, pageSize) => fileListChange(page, pageSize)} />
  424. </td>
  425. </tr>
  426. ) : null}
  427. </tbody>
  428. </table>
  429. <AuditContent data={auditData} onSelect={addAuditor} onDelete={delAuditor} />
  430. </div>
  431. </div>
  432. <OssUploadModal
  433. visible={modalObj.ossModal}
  434. onCancel={() => setModalObj({ ...modalObj, ossModal: false })}
  435. onCreate={onCreate}
  436. onShow={onOssModalShow} />
  437. <AuditModal
  438. type="safe"
  439. modalObj={modalProps}
  440. onCancel={() => setModalObj({ ...modalObj, auditModal: false })}
  441. onCreate={onModalConfirm}
  442. auditors={detail.auditors}
  443. curAuditor={detail.latestAuditor} />
  444. </div>
  445. )
  446. }
  447. export default Detail