index.tsx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  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, useCallback } from 'react'
  21. import { RouteComponentProps } from 'react-router'
  22. import { apiGetQualityDetail, apiResfulQualityAudit } from './api'
  23. import AuditModal from './components/Modal'
  24. import styles from './index.module.scss'
  25. const { TextArea } = Input
  26. interface iModalObj {
  27. ossModal: boolean
  28. auditModal: boolean
  29. auditType: string
  30. curPage: number
  31. loading: boolean
  32. }
  33. const Detail: React.FC<RouteComponentProps> = props => {
  34. const { id = '' } = props.location.state as any
  35. const [ modalObj, setModalObj ] = useState<iModalObj>({
  36. ossModal: false,
  37. auditModal: false,
  38. auditType: '',
  39. loading: false,
  40. curPage: 1
  41. })
  42. const [ detail, setDetail ] = useState<iDetailState>({
  43. auditName: '',
  44. uid: '',
  45. auditors: [],
  46. bidsectionId: '',
  47. code: '',
  48. createTime: new Date().toDateString(),
  49. demand: '',
  50. file: { fileList: [], total: 0 },
  51. id: '',
  52. inspection: '',
  53. inspectionDetail: '',
  54. position: '',
  55. status: 0,
  56. checkOrder: {
  57. // 整改单
  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. }, [ id ])
  84. const initData = async () => {
  85. const { code = -1, data = {} } = await apiGetQualityDetail(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.QUALITY, 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.QUALITY, 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.audit_id === 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. audit_order: 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 = useCallback((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 = async (values?: object) => {
  178. let payload: any = { quality_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 apiResfulQualityAudit(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. <ZhCloseButton size="small" onClick={() => btnClick('delete')}>
  230. 删除巡检
  231. </ZhCloseButton>
  232. <ZhSubmitButton size="small" className="pi-mg-left-5" onClick={() => btnClick('start')}>
  233. 提交审批
  234. </ZhSubmitButton>
  235. </div>
  236. )
  237. } else if (status === auditProgress.checking || status === auditProgress.checkNo) {
  238. return (
  239. <div className="pi-flex-row pi-align-center">
  240. <Button type="primary" danger size="small" onClick={() => btnClick('close')}>
  241. 关闭
  242. </Button>
  243. <ZhAuditBackButton size="small" className="pi-mg-left-5" onClick={() => btnClick('back')}>
  244. 审批退回
  245. </ZhAuditBackButton>
  246. <ZhSubmitButton size="small" className="pi-mg-left-5" onClick={() => btnClick('pass')}>
  247. 审批通过
  248. </ZhSubmitButton>
  249. </div>
  250. )
  251. } else if (status === auditProgress.checked) {
  252. return (
  253. <div className="pi-flex-row pi-align-center">
  254. <ZhAuditBackButton size="small" onClick={() => btnClick('back')}>
  255. 审批退回
  256. </ZhAuditBackButton>
  257. <ZhSubmitButton size="small" className="pi-mg-left-5" onClick={() => btnClick('pass')}>
  258. 整改完成
  259. </ZhSubmitButton>
  260. </div>
  261. )
  262. }
  263. }
  264. const modalProps = { visible: modalObj.auditModal, loading: modalObj.loading, type: modalObj.auditType }
  265. const auditData = { auditors: detail.auditors, auditHistory: detail.auditHistory, status: detail.status, uName: detail.auditName, uid: detail.uid }
  266. const hideAuditModal = () => {
  267. setModalObj({ ...modalObj, auditModal: false })
  268. }
  269. return (
  270. <div className="wrap-contaniner">
  271. <Header title="质量巡检">
  272. <Slot position="right">{renderHeaderBtn(detail.status)}</Slot>
  273. </Header>
  274. <div className={styles.detailContainer}>
  275. <div className={styles.content}>
  276. <h4 className={styles.header}>{detail.code}</h4>
  277. <table className="pi-table pi-bordered">
  278. <thead>
  279. <tr>
  280. <th colSpan={2} className="pi-text-center">
  281. 质量巡检单
  282. </th>
  283. </tr>
  284. </thead>
  285. <tbody>
  286. <tr>
  287. <th style={{ width: '150px' }}>检查项目</th>
  288. <td>
  289. {isEdited ? (
  290. <span>{detail.inspection}</span>
  291. ) : (
  292. <TextArea
  293. value={detail.inspection}
  294. onChange={e => {
  295. setDetail({ ...detail, inspection: e.currentTarget.value })
  296. }} />
  297. )}
  298. </td>
  299. </tr>
  300. <tr>
  301. <th style={{ width: '150px' }}>现场检查情况</th>
  302. <td>
  303. {isEdited ? (
  304. <span>{detail.inspectionDetail}</span>
  305. ) : (
  306. <TextArea
  307. value={detail.inspectionDetail}
  308. onChange={e => {
  309. setDetail({ ...detail, inspectionDetail: e.currentTarget.value })
  310. }} />
  311. )}
  312. </td>
  313. </tr>
  314. <tr>
  315. <th style={{ width: '150px' }}>处理要求及措施</th>
  316. <td>
  317. {isEdited ? (
  318. <span>{detail.demand}</span>
  319. ) : (
  320. <TextArea
  321. value={detail.demand}
  322. onChange={e => {
  323. setDetail({ ...detail, demand: e.currentTarget.value })
  324. }} />
  325. )}
  326. </td>
  327. </tr>
  328. <tr>
  329. <th style={{ width: '150px' }}>检查日期</th>
  330. <td>
  331. {isEdited ? (
  332. <span>{detail.createTime && dayjsFormat(detail.createTime, 'YYYY-MM-DD')}</span>
  333. ) : (
  334. <DatePicker
  335. size="small"
  336. locale={locale}
  337. allowClear={false}
  338. value={dayjs(detail.createTime)}
  339. onChange={value => setDetail({ ...detail, createTime: value?.format() })} />
  340. )}
  341. </td>
  342. </tr>
  343. <tr>
  344. <th style={{ width: '150px' }}>质检员</th>
  345. <td>{detail.auditName}</td>
  346. </tr>
  347. </tbody>
  348. </table>
  349. {detail.status === auditProgress.checked && detail.latestAuditor.auditId === userStore.userInfo.id ? (
  350. <table className="pi-table pi-bordered">
  351. <thead>
  352. <tr>
  353. <th colSpan={2} className="pi-text-center">
  354. 整改单
  355. </th>
  356. </tr>
  357. </thead>
  358. <tbody>
  359. <tr>
  360. <th style={{ width: '150px' }}>整改情况</th>
  361. <td>
  362. <TextArea
  363. value={detail.checkOrder.opinion}
  364. onChange={e => setDetail({ ...detail, checkOrder: { ...detail.checkOrder, opinion: e.currentTarget.value } })} />
  365. </td>
  366. </tr>
  367. <tr>
  368. <th style={{ width: '150px' }}>整改日期</th>
  369. <td>
  370. <DatePicker
  371. size="small"
  372. locale={locale}
  373. allowClear={false}
  374. value={dayjs(detail.checkOrder.create_time)}
  375. onChange={value => setDetail({ ...detail, checkOrder: { ...detail.checkOrder, create_time: value?.format() } })} />
  376. </td>
  377. </tr>
  378. <tr>
  379. <th style={{ width: '150px' }}>整改人</th>
  380. <td>{detail.auditors.find(item => item.progress === '1')?.name}</td>
  381. </tr>
  382. </tbody>
  383. </table>
  384. ) : (
  385. detail.rectifiedInfo.map(item => (
  386. <table className="pi-table pi-bordered" key={item.create_time}>
  387. <thead>
  388. <tr>
  389. <th colSpan={2} className="pi-text-center">
  390. 整改单
  391. </th>
  392. </tr>
  393. </thead>
  394. <tbody>
  395. <tr>
  396. <th style={{ width: '150px' }}>整改情况</th>
  397. <td>{item.opinion}</td>
  398. </tr>
  399. <tr>
  400. <th style={{ width: '150px' }}>整改日期</th>
  401. <td>{dayjsFormat(item.create_time, 'YYYY-MM-DD')}</td>
  402. </tr>
  403. <tr>
  404. <th style={{ width: '150px' }}>整改人</th>
  405. <td>{item.name}</td>
  406. </tr>
  407. </tbody>
  408. </table>
  409. ))
  410. )}
  411. <table className="pi-table pi-bordered mt-3">
  412. <thead>
  413. <tr>
  414. <th />
  415. <th className="pi-text-center">附件</th>
  416. <th className="pi-text-center">上传者</th>
  417. <th className="pi-text-center" style={{ width: 200 }}>
  418. 上传时间
  419. </th>
  420. <th className="pi-text-center">操作</th>
  421. </tr>
  422. </thead>
  423. <tbody>
  424. <tr>
  425. <td colSpan={5}>
  426. <ZhUploadButton size="small" icon={<SvgIcon type="xxh-cloud-upload" />} onClick={() => setModalObj({ ...modalObj, ossModal: true })}>
  427. 上传附件
  428. </ZhUploadButton>
  429. </td>
  430. </tr>
  431. {detail.file.fileList?.map((file, idx) => (
  432. <tr key={idx}>
  433. <td className="pi-width-70">{idx + 1}</td>
  434. <td style={{ width: 383, maxWidth: 383, overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }}>
  435. <a href={consts.OSS_PATH.REVIEW + file.filepath} target="_blank" rel="noopener noreferrer">
  436. {file.filename}
  437. </a>
  438. </td>
  439. <td className="pi-text-center pi-width-100">{file.accountName}</td>
  440. <td className="pi-text-center">{dayjsFormat(file.createTime)}</td>
  441. <td className="pi-text-center pi-width-90">
  442. <Tooltip title="移除">
  443. <Button size="small" type="text" icon={<SvgIcon type="xxh-times-circle1" />} style={{ color: '#df3f45' }} onClick={() => delFile(file.id, !idx)} />
  444. </Tooltip>
  445. </td>
  446. </tr>
  447. ))}
  448. {detail.file.total > consts.PAGE_SIZE ? (
  449. <tr>
  450. <td colSpan={5} className="pi-text-right">
  451. <Pagination
  452. current={modalObj.curPage}
  453. size="small"
  454. pageSize={consts.PAGE_SIZE}
  455. hideOnSinglePage={true}
  456. total={detail.file.total}
  457. onChange={(page, pageSize) => fileListChange(page, pageSize)} />
  458. </td>
  459. </tr>
  460. ) : null}
  461. </tbody>
  462. </table>
  463. <AuditContent data={auditData} onSelect={addAuditor} onDelete={delAuditor} />
  464. </div>
  465. </div>
  466. <OssUploadModal visible={modalObj.ossModal} onCancel={() => setModalObj({ ...modalObj, ossModal: false })} onCreate={onCreate} onShow={onOssModalShow} />
  467. <AuditModal type="quality" modalObj={modalProps} onCancel={hideAuditModal} onCreate={onModalConfirm} auditors={detail.auditors} curAuditor={detail.latestAuditor} />
  468. </div>
  469. )
  470. }
  471. export default Detail