BoilerInspectProject.vue 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953
  1. <template>
  2. <view class="inspect-project-container">
  3. <scroll-view class="scroll-list" scroll-y>
  4. <view class="report-list">
  5. <view v-for="([part, reportArr], index) in reportGroupedList" :key="index">
  6. <!-- 暂时注释掉的部件分组逻辑 -->
  7. <!-- <view v-if="part.id != null" class="part-card">
  8. <view class="part-card-header">
  9. <text class="part-card-header-text">{{ part.partName }}</text>
  10. <view class="part-card-header-btn-group">
  11. <view>复制</view>
  12. <view>增加</view>
  13. <view>删除</view>
  14. </view>
  15. </view>
  16. <view v-for="item in reportArr" :key="item.id" class="report-item">
  17. <view class="item-top" @click="handleSelectProject(item)">
  18. <view class="top-left">
  19. <radio
  20. :checked="selectedProjects.includes(item.id)"
  21. color="#4B8CD9"
  22. @click.stop="handleSelectProject(item)"
  23. />
  24. <text class="report-name">{{ item.reportName }}</text>
  25. <view
  26. class="type-tag"
  27. :style="{ backgroundColor: getReportTypeColor(item.reportType) }"
  28. >
  29. <text class="type-tag-text">{{ getReportTypeIcon(item.reportType) }}</text>
  30. </view>
  31. </view>
  32. <text class="status-text" :style="{ color: getStatusColor(item, useOnline) }">
  33. {{ getStatusText(item, useOnline) }}
  34. </text>
  35. </view>
  36. <view class="item-center">
  37. <view class="center-row">
  38. <text class="label">检验结论:</text>
  39. <view
  40. class="conclusion-box"
  41. :class="{
  42. 'conclusion-error': isConclusionError(getRecordJson(item).reportConclusion),
  43. }"
  44. @click="handleUpdateConclusion(item, 'reportConclusion')"
  45. >
  46. <text
  47. class="conclusion-text"
  48. :class="{
  49. 'conclusion-error-text': isConclusionError(
  50. getRecordJson(item).reportConclusion,
  51. ),
  52. }"
  53. >
  54. {{ getRecordJson(item).reportConclusion || '无' }}
  55. </text>
  56. </view>
  57. <view class="right-area">
  58. <view
  59. v-if="item.reportType === PressureReportType.SINGLE && item.instructionTempId"
  60. class="instruction-tag"
  61. :class="{ 'instruction-tag-error': !item.instructionId }"
  62. @click="handleAssociationOperation(item)"
  63. >
  64. <text
  65. class="instruction-text"
  66. :class="{ 'instruction-text-error': !item.instructionId }"
  67. >
  68. {{ item.instructionId ? '已' : '未' }}关联操作指导书
  69. </text>
  70. </view>
  71. <text v-if="item.reportType === 100" class="inspector-name">
  72. 主检人:{{ equipment?.mainCheckerUser?.nickname || '无' }}
  73. </text>
  74. <view
  75. v-else-if="!isOffline && canModifyChecker"
  76. class="inspector-btn"
  77. @click="handleExChangeItemChecker(item)"
  78. >
  79. <text class="inspector-label">检验员:</text>
  80. <text class="inspector-value">
  81. {{ item.checkUsers?.[0]?.nickname || '无' }}
  82. </text>
  83. </view>
  84. <text v-else class="inspector-name">
  85. 检验员:{{ item.checkUsers?.[0]?.nickname || '无' }}
  86. </text>
  87. </view>
  88. </view>
  89. <view class="center-row">
  90. <text class="label">检验结果:</text>
  91. <view class="result-box" @click="handleUpdateConclusion(item, 'reportResult')">
  92. <text class="result-text">{{ getRecordJson(item).reportResult || '-' }}</text>
  93. </view>
  94. </view>
  95. <view class="center-row">
  96. <text class="label">费用金额:</text>
  97. <view class="fee-area">
  98. <view
  99. v-if="
  100. (useOnline === '1' ? shouldShowFeeInput(item.taskStatus) : true) &&
  101. [
  102. PressureReportType.MAIN,
  103. PressureReportType.SUB,
  104. PressureReportType.SINGLE,
  105. ].includes(item.reportType)
  106. "
  107. class="fee-btns"
  108. >
  109. <view class="fee-value-box">
  110. <text class="fee-value">
  111. {{ typeof item.fee === 'number' && !isNaN(item.fee) ? item.fee : '0' }}
  112. </text>
  113. </view>
  114. <view class="fee-btn" @click="handleCalcFee(item)">
  115. <text class="fee-btn-text">费用录入</text>
  116. </view>
  117. </view>
  118. <text v-else class="fee-none">无</text>
  119. </view>
  120. <view class="action-btns">
  121. <template v-if="useOnline === '1'">
  122. <view
  123. v-if="shouldShowSign(item)"
  124. class="action-btn sign-btn"
  125. @click="handleSign(item)"
  126. >
  127. <text class="action-btn-text">签 名</text>
  128. </view>
  129. <view
  130. v-if="shouldShowAttachmentUpload(item.taskStatus)"
  131. class="action-btn attachment-btn"
  132. @click="handleUploadFile(item)"
  133. >
  134. <text class="action-btn-text">附件上传</text>
  135. </view>
  136. <view
  137. v-if="
  138. isEditable(item) &&
  139. shouldShowRecordInput(item.taskStatus) &&
  140. item.reportType !== PressureReportType.SUGGUESTION
  141. "
  142. class="action-btn record-btn"
  143. @click="handleRouteToInputWebview(item)"
  144. >
  145. <text class="action-btn-text">记录录入</text>
  146. </view>
  147. <view
  148. v-if="
  149. isEditable(item) &&
  150. shouldShowReInput(item.taskStatus) &&
  151. item.reportType !== PressureReportType.SUGGUESTION
  152. "
  153. class="action-btn re-input-btn"
  154. @click="handleRouteToInputWebview(item)"
  155. >
  156. <text class="action-btn-text">重新录入</text>
  157. </view>
  158. <view
  159. v-if="isEditable(item) && shouldShowSubmitCheck(item.taskStatus)"
  160. class="action-btn submit-check-btn"
  161. @click="handleSubmitCheck(item)"
  162. >
  163. <text class="action-btn-text">提交校核</text>
  164. </view>
  165. <view
  166. v-if="isEditable(item) && shouldShowSubmitReport(item.taskStatus)"
  167. class="action-btn submit-report-btn"
  168. @click="showSelectReportUserPopup(item)"
  169. >
  170. <text class="action-btn-text">提交报告</text>
  171. </view>
  172. </template>
  173. <template v-else>
  174. <view class="action-btn attachment-btn" @click="handleUploadFile(item)">
  175. <text class="action-btn-text">附件上传</text>
  176. </view>
  177. <view
  178. v-if="item.reportType !== PressureReportType.SUGGUESTION"
  179. class="action-btn record-btn"
  180. @click="handleRouteToInputWebview(item)"
  181. >
  182. <text class="action-btn-text">
  183. {{ !item.localReportUrl ? '记录录入' : '重新录入' }}
  184. </text>
  185. </view>
  186. <view
  187. class="action-btn submit-check-btn"
  188. @click="uploadFileAndSubmitCheckItemCallback.callback(item)"
  189. >
  190. <text class="action-btn-text">
  191. {{ item.hasSyncedData ? '重新上传' : '上传' }}
  192. </text>
  193. </view>
  194. <view
  195. v-if="
  196. item.reportType !== PressureReportType.SUGGUESTION &&
  197. ((useOnline === '1' && item.reportUrl) ||
  198. (useOnline !== '1' &&
  199. item.hasSyncedData &&
  200. item.localReportUrl &&
  201. item.reportUrl))
  202. "
  203. class="action-btn submit-check-btn"
  204. @click="handleSubmitCheck(item)"
  205. >
  206. <text class="action-btn-text">提交校核</text>
  207. </view>
  208. </template>
  209. </view>
  210. </view>
  211. </view>
  212. </view>
  213. </view> -->
  214. <!-- 如果开放了部件分组的模板,这里要加上一个 v-else 指令 -->
  215. <view v-for="item in reportArr" :key="item.id" class="report-item">
  216. <view class="item-top" @click="handleSelectProject(item)">
  217. <view class="top-left">
  218. <radio
  219. :checked="selectedProjects.includes(item.id)"
  220. color="#4B8CD9"
  221. @click.stop="handleSelectProject(item)"
  222. />
  223. <text class="report-name">{{ item.reportName }}</text>
  224. <view
  225. class="type-tag"
  226. :style="{ backgroundColor: getReportTypeColor(item.reportType) }"
  227. >
  228. <text class="type-tag-text">{{ getReportTypeIcon(item.reportType) }}</text>
  229. </view>
  230. </view>
  231. <text class="status-text" :style="{ color: getStatusColor(item, useOnline) }">
  232. {{ getStatusText(item, useOnline) }}
  233. </text>
  234. </view>
  235. <view class="item-center">
  236. <view class="center-row">
  237. <text class="label">检验结论:</text>
  238. <view
  239. class="conclusion-box"
  240. :class="{
  241. 'conclusion-error': isConclusionError(item.reportConclusion),
  242. }"
  243. @click="handleUpdateConclusion(item, 'reportConclusion')"
  244. >
  245. <text
  246. class="conclusion-text"
  247. :class="{
  248. 'conclusion-error-text': isConclusionError(
  249. item.reportConclusion
  250. ),
  251. }"
  252. >
  253. {{ item.reportConclusion || '无' }}
  254. </text>
  255. </view>
  256. <view class="right-area">
  257. <view
  258. v-if="item.reportType === PressureReportType.SINGLE && item.instructionTempId"
  259. class="instruction-tag"
  260. :class="{ 'instruction-tag-error': !item.instructionId }"
  261. @click="handleAssociationOperation(item)"
  262. >
  263. <text
  264. class="instruction-text"
  265. :class="{ 'instruction-text-error': !item.instructionId }"
  266. >
  267. {{ item.instructionId ? '已' : '未' }}关联操作指导书
  268. </text>
  269. </view>
  270. <text v-if="item.reportType === 100" class="inspector-name">
  271. 主检人:{{ equipment?.mainCheckerUser?.nickname || '无' }}
  272. </text>
  273. <view
  274. v-else-if="!isOffline && canModifyChecker"
  275. class="inspector-btn"
  276. @click="handleExChangeItemChecker(item)"
  277. >
  278. <text class="inspector-label">检验员:</text>
  279. <text class="inspector-value">
  280. {{ item.checkUsers?.[0]?.nickname || '无' }}
  281. </text>
  282. </view>
  283. <text v-else class="inspector-name">
  284. 检验员:{{ item.checkUsers?.[0]?.nickname || '无' }}
  285. </text>
  286. </view>
  287. </view>
  288. <view class="center-row">
  289. <text class="label">检验结果:</text>
  290. <view class="result-box" @click="handleUpdateConclusion(item, 'reportResult')">
  291. <text class="result-text">{{ item.reportResult || '-' }}</text>
  292. </view>
  293. </view>
  294. <view class="center-row">
  295. <text class="label">费用金额:</text>
  296. <view class="fee-area">
  297. <view
  298. v-if="
  299. (useOnline === '1' ? shouldShowFeeInput(item.taskStatus) : true) &&
  300. [
  301. PressureReportType.MAIN,
  302. PressureReportType.SUB,
  303. PressureReportType.SINGLE,
  304. ].includes(item.reportType)
  305. "
  306. class="fee-btns"
  307. >
  308. <view class="fee-value-box">
  309. <text class="fee-value">
  310. {{ typeof item.fee === 'number' && !isNaN(item.fee) ? item.fee : '0' }}
  311. </text>
  312. </view>
  313. <view class="fee-btn" @click="handleCalcFee(item)">
  314. <text class="fee-btn-text">费用录入</text>
  315. </view>
  316. </view>
  317. <text v-else class="fee-none">无</text>
  318. </view>
  319. <view class="action-btns">
  320. <template v-if="useOnline === '1'">
  321. <view
  322. v-if="shouldShowSign(item)"
  323. class="action-btn sign-btn"
  324. @click="handleSign(item)"
  325. >
  326. <text class="action-btn-text">签 名</text>
  327. </view>
  328. <view
  329. v-if="shouldShowAttachmentUpload(item.taskStatus)"
  330. class="action-btn attachment-btn"
  331. @click="handleUploadFile(item)"
  332. >
  333. <text class="action-btn-text">附件上传</text>
  334. </view>
  335. <view
  336. v-if="
  337. isEditable(item) &&
  338. shouldShowRecordInput(item.taskStatus) &&
  339. item.reportType !== PressureReportType.SUGGUESTION
  340. "
  341. class="action-btn record-btn"
  342. @click="handleRouteToInputWebview(item)"
  343. >
  344. <text class="action-btn-text">记录录入</text>
  345. </view>
  346. <view
  347. v-if="
  348. isEditable(item) &&
  349. shouldShowReInput(item.taskStatus) &&
  350. item.reportType !== PressureReportType.SUGGUESTION
  351. "
  352. class="action-btn re-input-btn"
  353. @click="handleRouteToInputWebview(item)"
  354. >
  355. <text class="action-btn-text">重新录入</text>
  356. </view>
  357. <view
  358. v-if="isEditable(item) && shouldShowSubmitCheck(item.taskStatus)"
  359. class="action-btn submit-check-btn"
  360. @click="handleSubmitCheck(item)"
  361. >
  362. <text class="action-btn-text">提交校核</text>
  363. </view>
  364. <view
  365. v-if="isEditable(item) && shouldShowSubmitReport(item.taskStatus)"
  366. class="action-btn submit-report-btn"
  367. @click="showSelectReportUserPopup(item)"
  368. >
  369. <text class="action-btn-text">提交报告</text>
  370. </view>
  371. </template>
  372. <template v-else>
  373. <view class="action-btn attachment-btn" @click="handleUploadFile(item)">
  374. <text class="action-btn-text">附件上传</text>
  375. </view>
  376. <view
  377. v-if="item.reportType !== PressureReportType.SUGGUESTION"
  378. class="action-btn record-btn"
  379. @click="handleRouteToInputWebview(item)"
  380. >
  381. <text class="action-btn-text">
  382. {{ !item.localReportUrl ? '记录录入' : '重新录入' }}
  383. </text>
  384. </view>
  385. <view
  386. class="action-btn submit-check-btn"
  387. @click="uploadFileAndSubmitCheckItemCallback.callback(item)"
  388. >
  389. <text class="action-btn-text">
  390. {{ item.hasSyncedData ? '重新上传' : '上传' }}
  391. </text>
  392. </view>
  393. <view
  394. v-if="
  395. item.reportType !== PressureReportType.SUGGUESTION &&
  396. ((useOnline === '1' && item.reportUrl) ||
  397. (useOnline !== '1' &&
  398. item.hasSyncedData &&
  399. item.localReportUrl &&
  400. item.reportUrl))
  401. "
  402. class="action-btn submit-check-btn"
  403. @click="handleSubmitCheck(item)"
  404. >
  405. <text class="action-btn-text">提交校核</text>
  406. </view>
  407. </template>
  408. </view>
  409. </view>
  410. </view>
  411. </view>
  412. </view>
  413. <view v-if="!reportGroupedList || reportGroupedList.size === 0" class="empty-state">
  414. <text class="empty-text">暂无检验项目</text>
  415. </view>
  416. </view>
  417. </scroll-view>
  418. <view class="bottom-bar">
  419. <view class="select-all-box" @click="handleSelectAll">
  420. <checkbox
  421. class="square-checkbox"
  422. :checked="selectAll"
  423. color="#4B8CD9"
  424. @click.stop="handleSelectAll"
  425. />
  426. <text class="select-all-text">{{ selectAll ? '取消全选' : '全选' }}</text>
  427. </view>
  428. <view class="action-buttons">
  429. <view class="operate-btn delete-btn" @click="handleDelReport">
  430. <text class="operate-btn-text">作废项目</text>
  431. </view>
  432. <view class="operate-btn guide-btn" @click="showAddWorkInstructionPopup">
  433. <text class="operate-btn-text">添加指导书</text>
  434. </view>
  435. <!-- 电站锅炉的添加部件按钮 -->
  436. <!-- <view
  437. class="operate-btn part-btn"
  438. v-if="equipment?.boilerType === '1' && taskOrder?.checkType === 100"
  439. >
  440. <text class="operate-btn-text">添加部件</text>
  441. </view> -->
  442. <view class="operate-btn add-btn" @click="showCheckProjectPopup">
  443. <text class="operate-btn-text">添加项目</text>
  444. </view>
  445. <view v-if="useOnline !== '1'" class="operate-btn upload-btn" @click="handleCheckReport">
  446. <text class="operate-btn-text">上传</text>
  447. </view>
  448. </view>
  449. </view>
  450. <TipsPopup ref="tipsPopupRef" />
  451. <CalcCheckItemPopup
  452. v-if="showCalcPopup"
  453. ref="calcCheckItemPopupRef"
  454. :check-item="selectedCheckItem"
  455. @hide="hideCalcPopup"
  456. @update-render-item="handleUpdateRenderItem"
  457. />
  458. <ExchangeChecker
  459. v-if="showExchangePopup"
  460. ref="exchangeCheckerPopupRef"
  461. :check-users="dataSource?.checkUsers"
  462. :current-checker="editCheckItem?.checkUsers?.[0]"
  463. :check-item-id="currentCheckItemId"
  464. @hide="hideExchangePopup"
  465. @update-check-item-checker="handleUpdateChecker"
  466. />
  467. <UpdateConclusionPopup
  468. v-if="showConclusionPopup"
  469. ref="updateConclusionPopupRef"
  470. :check-item="currentCheckItem"
  471. :field-key="currentFieldKey"
  472. @hide="hideConclusionPopup"
  473. @confirm="handleUpdateConclusionConfirm"
  474. />
  475. <view v-if="showSelectReportPopup" class="popup-overlay" @click="closeSelectReportPopup">
  476. <view class="popup-content" @click.stop>
  477. <text class="popup-title">确认提交报告?</text>
  478. <!-- <text class="popup-title">选择审核人</text>
  479. <picker :range="recheckUserGroupList" range-key="label" @change="onReportUserChange">
  480. <view class="picker-value">
  481. <text>{{ currentReckUser?.label || '请选择' }}</text>
  482. </view>
  483. </picker> -->
  484. <view class="popup-actions">
  485. <button class="action-btn cancel-btn" @click="closeSelectReportPopup">取消</button>
  486. <button class="action-btn confirm-btn" @click="handleSubmitReport">确定</button>
  487. </view>
  488. </view>
  489. </view>
  490. <view v-if="showSelectTemplatePopup" class="popup-overlay" @click="closeSelectTemplatePopup">
  491. <view class="popup-content" @click.stop>
  492. <text class="popup-title">选择模板</text>
  493. <picker :range="templateList" range-key="label" @change="onTemplateChange">
  494. <view class="picker-value">
  495. <text>{{ selectedTemplate?.label || '请选择' }}</text>
  496. </view>
  497. </picker>
  498. <view class="popup-actions">
  499. <button class="action-btn cancel-btn" @click="closeSelectTemplatePopup">取消</button>
  500. <button class="action-btn confirm-btn" @click="handleConfirmTemplate">确定</button>
  501. </view>
  502. </view>
  503. </view>
  504. </view>
  505. </template>
  506. <script lang="ts" setup>
  507. import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
  508. import {
  509. PressureCheckerMyTaskStatus,
  510. PressureReportType,
  511. PressureCheckerMyTaskStatusMap,
  512. } from '@/utils/dictMap'
  513. import { isCheckItemEditable, isAssignedToOthers } from '@/utils/equipmentPermissions'
  514. import {
  515. getApprovalDetail,
  516. getUserGroupUserList,
  517. pressure2NotVerifyPageApi,
  518. } from '@/api/task'
  519. import { cancelBoilerInSpectProject, addBoilerMajorIssues, submitBoilerReport } from '@/api/boiler/boilerTaskOrder'
  520. import { updateBoilerTaskOrderItemReportConclusion } from '@/api/boiler/boilerTaskOrderReport'
  521. import TipsPopup from './inspectProjectComponent/TipsPopup.vue'
  522. import CalcCheckItemPopup from './inspectProject/component/calcCheckItemPopup.vue'
  523. import ExchangeChecker from './inspectProject/component/ExchangeChecker.vue'
  524. import UpdateConclusionPopup from './inspectProject/component/UpdateConclusionPopup.vue'
  525. import eventBus from '@/utils/eventBus'
  526. import { useUserStore } from '@/store/user'
  527. interface CheckConclusionItem {
  528. value: string
  529. id: string
  530. }
  531. interface CheckItem {
  532. id: string
  533. reportName: string
  534. localReportUrl?: string
  535. prepareJson?: string | object
  536. reportType?: number
  537. recordJson?: string
  538. [key: string]: any
  539. }
  540. interface Props {
  541. reportList: CheckItem[]
  542. taskOrder: StringAnyObj
  543. equipment: StringAnyObj
  544. dataSource: any
  545. orderId: string
  546. orderItemId: string
  547. useOnline?: string
  548. isMainChecker?: boolean
  549. canModifyAssignments?: boolean
  550. updateReportList?: (reportList: CheckItem[]) => void
  551. fetchGetEquipmentDetail?: () => void
  552. refreshDetail?: () => void
  553. showSelectUserPopup?: (checkItem: CheckItem) => void
  554. handleAssociationOperationManual?: (checkItem: CheckItem) => void
  555. showCheckProjectPopupFn?: () => void
  556. }
  557. const props = withDefaults(defineProps<Props>(), {
  558. reportList: () => [],
  559. useOnline: '1',
  560. dataSource: {
  561. partList: [],
  562. },
  563. isMainChecker: false,
  564. canModifyAssignments: false,
  565. })
  566. const reportGroupedList = computed(() => {
  567. const groupedList: Map<any, any> = new Map()
  568. if (!props.dataSource?.partList) {
  569. return groupedList
  570. }
  571. // 按照 partType 排序
  572. const sortedPartList = [...props.dataSource.partList].sort((a, b) => {
  573. const typeA = parseInt(a.partType) || 0
  574. const typeB = parseInt(b.partType) || 0
  575. return typeA - typeB
  576. })
  577. sortedPartList.forEach((item) => {
  578. if (!groupedList.has(item)) {
  579. groupedList.set(item, [])
  580. }
  581. // 如果item.id为空,添加itemPartId为'0'或空的报告
  582. if (!item.id) {
  583. const equipReport = props.reportList.filter(
  584. (report) => !report.itemPartId || report.itemPartId === '0',
  585. )
  586. groupedList.set(item, equipReport)
  587. } else {
  588. const partReport = props.reportList.filter((report) => report.itemPartId === item.id)
  589. groupedList.set(item, partReport)
  590. }
  591. })
  592. return groupedList
  593. })
  594. const emit = defineEmits<{
  595. updateReportList: [list: CheckItem[]]
  596. refresh: []
  597. }>()
  598. const selectAll = ref(false)
  599. const selectedProjects = ref<string[]>([])
  600. const selectedCheckItem = ref<any>(null)
  601. const editCheckItem = ref<any>(null)
  602. const currentCheckItemId = ref('')
  603. const currentCheckItem = ref<any>(null)
  604. const currentFieldKey = ref<'reportConclusion' | 'reportResult'>('reportConclusion')
  605. const refreshing = ref(false)
  606. const isOffline = ref(false)
  607. const showCalcPopup = ref(false)
  608. const showExchangePopup = ref(false)
  609. const showConclusionPopup = ref(false)
  610. const showSelectReportPopup = ref(false)
  611. const tipsPopupRef = ref<any>(null)
  612. const calcCheckItemPopupRef = ref<any>(null)
  613. const exchangeCheckerPopupRef = ref<any>(null)
  614. const updateConclusionPopupRef = ref<any>(null)
  615. const currentReportItem = ref<any>(null)
  616. const recheckUserGroupList = ref<any[]>([])
  617. const currentReckUser = ref<any>(null)
  618. const showSelectTemplatePopup = ref(false)
  619. const templateList = ref<any[]>([])
  620. const selectedTemplate = ref<any>(null)
  621. const userInfo = computed(() => useUserStore().userInfo)
  622. const canModifyChecker = computed(() => {
  623. return props.canModifyAssignments && props.isMainChecker
  624. })
  625. const isNetworkConnected = ref(true)
  626. onMounted(() => {
  627. checkNetworkStatus()
  628. initSelected()
  629. setupEventListeners()
  630. })
  631. onUnmounted(() => {
  632. removeEventListeners()
  633. })
  634. const checkNetworkStatus = () => {
  635. isOffline.value = !isNetworkConnected.value
  636. }
  637. const setupEventListeners = () => {
  638. eventBus.on('RefreshInspectProject', () => {
  639. initSelected()
  640. props.fetchGetEquipmentDetail?.()
  641. })
  642. eventBus.on('webViewSaved', (data: any) => {
  643. initSelected()
  644. if (props.useOnline === '1' && data.isOnline) {
  645. const updatedReportList = (props.reportList || []).map((item: any) => {
  646. if (item.id === data.checkItemId) {
  647. return {
  648. ...item,
  649. reportUrl: data.reportUrl,
  650. bindingPathSchema: data.bindingPathSchema,
  651. prepareJson: data.prepareJson,
  652. }
  653. }
  654. return item
  655. })
  656. emit('updateReportList', updatedReportList)
  657. props.refreshDetail?.()
  658. } else {
  659. props.fetchGetEquipmentDetail?.()
  660. }
  661. })
  662. uni.$on('onNetworkStatusChange', (connected: boolean) => {
  663. isOffline.value = !connected
  664. })
  665. }
  666. const removeEventListeners = () => {
  667. eventBus.off('RefreshInspectProject')
  668. eventBus.off('webViewSaved')
  669. uni.$off('onNetworkStatusChange')
  670. }
  671. const onRefresh = async () => {
  672. if (props.useOnline !== '1') return
  673. refreshing.value = true
  674. try {
  675. props.refreshDetail?.()
  676. } catch (error) {
  677. console.error('下拉刷新失败:', error)
  678. } finally {
  679. refreshing.value = false
  680. }
  681. }
  682. const initSelected = () => {
  683. selectAll.value = false
  684. selectedProjects.value = []
  685. }
  686. const handleSelectAll = () => {
  687. selectAll.value = !selectAll.value
  688. if (selectAll.value) {
  689. selectedProjects.value = props.reportList.map((item) => item.id)
  690. } else {
  691. selectedProjects.value = []
  692. }
  693. }
  694. const handleSelectProject = (item: CheckItem) => {
  695. const index = selectedProjects.value.indexOf(item.id)
  696. if (index === -1) {
  697. selectedProjects.value.push(item.id)
  698. } else {
  699. selectedProjects.value.splice(index, 1)
  700. }
  701. selectAll.value = selectedProjects.value.length === props.reportList.length
  702. }
  703. const getRecordJson = (item: CheckItem): Record<string, any> => {
  704. try {
  705. return item.recordJson && typeof item.recordJson === 'string' ? JSON.parse(item.recordJson) : {}
  706. } catch {
  707. return {}
  708. }
  709. }
  710. const isConclusionError = (conclusion: string): boolean => {
  711. return /不合格|不符合/.test(conclusion || '')
  712. }
  713. const getReportTypeIcon = (reportType?: number): string | null => {
  714. switch (reportType) {
  715. case PressureReportType.MAIN:
  716. return '主'
  717. case PressureReportType.SINGLE:
  718. return '独'
  719. case PressureReportType.SUB:
  720. return '子'
  721. case PressureReportType.SUGGUESTION:
  722. case PressureReportType.MAINQUESTION:
  723. case PressureReportType.WORKINSTRUCTION:
  724. return '附'
  725. default:
  726. return null
  727. }
  728. }
  729. const getReportTypeColor = (reportType?: number): string => {
  730. switch (reportType) {
  731. case PressureReportType.MAIN:
  732. return 'rgb(68,127,241)'
  733. case PressureReportType.SINGLE:
  734. return 'rgb(230,162,60)'
  735. case PressureReportType.SUB:
  736. return 'rgb(145,213,255)'
  737. case PressureReportType.SUGGUESTION:
  738. case PressureReportType.MAINQUESTION:
  739. case PressureReportType.WORKINSTRUCTION:
  740. return '#fff'
  741. default:
  742. return '#fff'
  743. }
  744. }
  745. const getStatusText = (item: CheckItem, useOnline?: string): string => {
  746. if (useOnline === '1') {
  747. if (
  748. item.taskStatus === PressureCheckerMyTaskStatus.RECORD_CHECK &&
  749. item.recheckStatus === 200
  750. ) {
  751. return '校核已通过'
  752. }
  753. return PressureCheckerMyTaskStatusMap[item.taskStatus] || '待录入'
  754. } else {
  755. return !item.localReportUrl ? '待录入' : '已录入'
  756. }
  757. }
  758. const getStatusColor = (item: CheckItem, useOnline?: string): string => {
  759. if (useOnline === '1') {
  760. if (
  761. item.taskStatus === PressureCheckerMyTaskStatus.RECORD_CHECK &&
  762. item.recheckStatus === 200
  763. ) {
  764. return '#52C41A'
  765. }
  766. return '#4B8CD9'
  767. } else {
  768. return !item.localReportUrl ? '#FF3535' : '#4B8CD9'
  769. }
  770. }
  771. const shouldShowFeeInput = (taskStatus?: number): boolean => {
  772. return taskStatus !== undefined
  773. }
  774. const shouldShowSign = (item: CheckItem): boolean => {
  775. return item.isSignFunction === 1 && item.taskStatus === PressureCheckerMyTaskStatus.RECORD_INPUT
  776. }
  777. const shouldShowAttachmentUpload = (taskStatus?: number): boolean => {
  778. // return taskStatus !== undefined
  779. return false
  780. }
  781. const shouldShowRecordInput = (taskStatus?: number): boolean => {
  782. return taskStatus === PressureCheckerMyTaskStatus.CONFIRMED
  783. }
  784. const shouldShowReInput = (taskStatus?: number): boolean => {
  785. return taskStatus === PressureCheckerMyTaskStatus.RECORD_INPUT
  786. }
  787. const shouldShowSubmitCheck = (taskStatus?: number): boolean => {
  788. return taskStatus === PressureCheckerMyTaskStatus.RECORD_INPUT
  789. }
  790. const shouldShowSubmitReport = (taskStatus?: number): boolean => {
  791. return taskStatus === PressureCheckerMyTaskStatus.REPORT_INPUT
  792. }
  793. const isEditable = (item: CheckItem): boolean => {
  794. return isCheckItemEditable(item, userInfo.value, props.equipment)
  795. }
  796. const isAssignedToOther = (item: CheckItem): boolean => {
  797. return isAssignedToOthers(item, userInfo.value)
  798. }
  799. const handleRouteToInputWebview = (item: CheckItem) => {
  800. const userId =
  801. item.reportType === PressureReportType.MAIN
  802. ? props.equipment?.mainCheckerUser?.id
  803. : item?.checkUsers?.[0]?.id
  804. if (userId && userId !== userInfo.value?.id) {
  805. uni.showToast({ title: '已分配项目不支持记录录入', icon: 'error' })
  806. return
  807. }
  808. uni.navigateTo({
  809. url: `/pages/editor/equipCheckRecordEditor?userId=${userInfo.value?.id}&orderItemId=${props.orderItemId}&checkItemId=${item?.id}&templateId=${item.templateId}&equipCode=${item.equipCode}&useOnline=${props.useOnline}&reportUrl=${item.reportUrl || ''}`,
  810. })
  811. }
  812. const handleCalcFee = (item: CheckItem) => {
  813. const mainCheckerId =
  814. item.reportType === PressureReportType.MAIN
  815. ? props.equipment?.mainCheckerUser?.id
  816. : item?.checkUsers?.[0]?.id
  817. if (mainCheckerId && mainCheckerId !== userInfo.value?.id) {
  818. uni.showToast({ title: '只能主检人进行费用录入', icon: 'error' })
  819. return
  820. }
  821. if (item.taskStatus === PressureCheckerMyTaskStatus.REPORT_END) {
  822. uni.showToast({ title: '报告已办结,不能录入费用', icon: 'error' })
  823. return
  824. }
  825. selectedCheckItem.value = item
  826. showCalcPopup.value = true
  827. }
  828. const hideCalcPopup = () => {
  829. showCalcPopup.value = false
  830. selectedCheckItem.value = null
  831. }
  832. const handleUpdateRenderItem = async (calcData: any) => {
  833. await updateBoilerTaskOrderItemReportConclusion(calcData)
  834. const report = props.reportList.find(item => item.id === calcData.id)
  835. const feeKey = 'fee'
  836. report[feeKey] = calcData[feeKey]
  837. props.refreshDetail()
  838. showCalcPopup.value = false
  839. // const updatedList = props.reportList.map((x) => {
  840. // if (x.id === calcData.id) {
  841. // return {
  842. // ...x,
  843. // fee: calcData.fee,
  844. // feeCalculateJson: calcData.feeCalculateJson,
  845. // }
  846. // }
  847. // return x
  848. // })
  849. // emit('updateReportList', updatedList)
  850. // initSelected()
  851. }
  852. const handleExChangeItemChecker = (item: CheckItem) => {
  853. if (item.isLocal) {
  854. uni.showToast({ title: '本地项目不能修改检验员,请上传', icon: 'error' })
  855. return
  856. }
  857. editCheckItem.value = item
  858. currentCheckItemId.value = item.id
  859. showExchangePopup.value = true
  860. }
  861. const hideExchangePopup = () => {
  862. showExchangePopup.value = false
  863. editCheckItem.value = null
  864. currentCheckItemId.value = ''
  865. }
  866. const handleUpdateChecker = async (checker: any, checkItemId: string) => {
  867. console.log('checker......', checker, checkItemId)
  868. // try {
  869. // const newChecker = typeof checker === 'string' ? JSON.parse(checker) : checker
  870. // if (!newChecker.id) {
  871. // console.warn('Checker missing id')
  872. // return
  873. // }
  874. // const { handleUpdateReportCheckUsers } = await import('@/api/task')
  875. // const result = await handleUpdateReportCheckUsers(newChecker, checkItemId)
  876. // if (result) {
  877. // uni.showToast({ title: '更新检验员成功' })
  878. // hideExchangePopup()
  879. // props.refreshDetail?.()
  880. // }
  881. // } catch (error) {
  882. // console.error('更新检验员失败:', error)
  883. // uni.showToast({ title: '更新检验员失败', icon: 'error' })
  884. // }
  885. }
  886. const handleUpdateConclusion = (item: CheckItem, fieldKey: 'reportConclusion' | 'reportResult') => {
  887. if (item.isLocal) {
  888. uni.showToast({ title: '本地项目不能修改检验结论', icon: 'error' })
  889. return
  890. }
  891. const canUpdate = [
  892. PressureCheckerMyTaskStatus.CONFIRMED,
  893. PressureCheckerMyTaskStatus.RECORD_INPUT,
  894. ].includes(item.taskStatus)
  895. if (!canUpdate) return
  896. currentCheckItem.value = item
  897. currentFieldKey.value = fieldKey
  898. showConclusionPopup.value = true
  899. }
  900. const hideConclusionPopup = () => {
  901. showConclusionPopup.value = false
  902. currentCheckItem.value = null
  903. currentFieldKey.value = 'reportConclusion'
  904. }
  905. const handleUpdateConclusionConfirm = async (conclusionData: any) => {
  906. await updateBoilerTaskOrderItemReportConclusion(conclusionData)
  907. // 先前端缓存结果,让响应更快,从而可以不引入Loading动画表达中间过渡
  908. for (const key in conclusionData) {
  909. if (!Object.hasOwn(conclusionData, key)) continue;
  910. const element = conclusionData[key];
  911. currentCheckItem.value[key] = element
  912. }
  913. props.refreshDetail()
  914. showConclusionPopup.value = false
  915. }
  916. const handleSubmitCheck = (item: CheckItem) => {
  917. const { isSignFunction, signFunctionUrl, reportType } = item
  918. if (isSignFunction === 1 && !signFunctionUrl) {
  919. uni.showToast({
  920. title: '客户未签名,不支持提交校核',
  921. icon: 'none',
  922. })
  923. return
  924. }
  925. props.showSelectUserPopup?.(item)
  926. }
  927. const showSelectReportUserPopup = async (item: CheckItem) => {
  928. if (props.useOnline !== '1') {
  929. uni.showToast({ title: '离线模式不支持提交报告', icon: 'none' })
  930. return
  931. }
  932. currentReportItem.value = item
  933. // await getAuditUserGroupList(item)
  934. showSelectReportPopup.value = true
  935. }
  936. const getAuditUserGroupList = async (checkItem: any) => {
  937. try {
  938. const groupRes: any = await getApprovalDetail({})
  939. const { recheckId, approveUser, yearCheckRecheckJson } = groupRes?.data || {}
  940. const yearCheckRecheckJsonData = yearCheckRecheckJson ? JSON.parse(yearCheckRecheckJson) : {}
  941. const { checkType } = props.taskOrder || {}
  942. const { reportType } = checkItem || {}
  943. const isYearCheckType = checkType === 200 && reportType === PressureReportType.MAIN
  944. const category = isYearCheckType ? 201 : 200
  945. const filterId = isYearCheckType ? yearCheckRecheckJsonData.yearCheckRecordAuditId : recheckId
  946. const userRes: any = await getUserGroupUserList({ category, status: 0 })
  947. const list = (userRes?.data || []).map((item: any) => ({
  948. ...item,
  949. label: item.nickname,
  950. value: item.id,
  951. }))
  952. const defaultItem = list.find((item: any) => item.value === filterId) || {}
  953. currentReckUser.value = defaultItem
  954. recheckUserGroupList.value = list
  955. } catch (error) {
  956. console.error('获取审核人列表失败:', error)
  957. uni.showToast({ title: '获取审核人列表失败', icon: 'error' })
  958. }
  959. }
  960. const onReportUserChange = (e: any) => {
  961. const index = e.detail.value
  962. currentReckUser.value = recheckUserGroupList.value[index]
  963. }
  964. const handleSubmitReport = async () => {
  965. // if (!currentReckUser.value) {
  966. // uni.showToast({ title: '请选择审核人', icon: 'error' })
  967. // return
  968. // }
  969. if (!currentReportItem.value) {
  970. uni.showToast({ title: '报告信息丢失,请重试', icon: 'error' })
  971. return
  972. }
  973. try {
  974. uni.showLoading({ title: '提交中...' })
  975. const params = {
  976. id: currentReportItem.value.id,
  977. approveId: userInfo.value?.id,
  978. reportType: currentReportItem.value.reportType,
  979. }
  980. const result: any = await submitBoilerReport(params)
  981. uni.hideLoading()
  982. if (result?.code === 0) {
  983. uni.showToast({ title: '提交报告成功', icon: 'success' })
  984. showSelectReportPopup.value = false
  985. props.fetchGetEquipmentDetail?.()
  986. props.refreshDetail?.()
  987. currentReportItem.value = null
  988. } else {
  989. uni.showToast({ title: result?.msg || '提交失败', icon: 'error' })
  990. }
  991. } catch (error: any) {
  992. uni.hideLoading()
  993. console.error('提交报告失败:', error)
  994. uni.showToast({ title: error?.msg || '提交失败,请重试', icon: 'error' })
  995. }
  996. }
  997. const closeSelectReportPopup = () => {
  998. showSelectReportPopup.value = false
  999. currentReportItem.value = null
  1000. }
  1001. const handleSign = (item: CheckItem) => {
  1002. const { unitContact, unitPhone } = props.taskOrder || {}
  1003. uni.navigateTo({
  1004. url: `/pages/sign-report/index?reportId=${item.id}&pushName=${unitContact}&pushPhone=${unitPhone}`,
  1005. })
  1006. }
  1007. const handleUploadFile = (item: CheckItem) => {
  1008. const userId =
  1009. item.reportType === PressureReportType.MAIN
  1010. ? props.equipment?.mainCheckerUser?.id
  1011. : item?.checkUsers?.[0]?.id
  1012. const readOnly = isAssignedToOther(item)
  1013. const fileList = props.reportList?.map((i: any) => ({
  1014. checkItemId: i?.id,
  1015. reportName: i?.reportName,
  1016. image: i.image,
  1017. video: i.video,
  1018. attachment: i.attachment,
  1019. }))
  1020. uni.navigateTo({
  1021. url: `/pages/uploadFile/UploadFile?isEdit=${!readOnly}&checkItemId=${item?.id}&fileList=${encodeURIComponent(JSON.stringify(fileList))}&useOnline=${props.useOnline}&readOnly=${readOnly}`,
  1022. })
  1023. }
  1024. const handleAssociationOperation = (item: CheckItem) => {
  1025. props.handleAssociationOperationManual?.(item)
  1026. }
  1027. const showAddWorkInstructionPopup = async () => {
  1028. try {
  1029. const result = await pressure2NotVerifyPageApi({ reportType: PressureReportType.WORKINSTRUCTION, status: 200, pageNo: 1, pageSize: 9999 })
  1030. const list = (result?.data || []).map((item: any) => ({
  1031. ...item,
  1032. label: item.tbName || '',
  1033. value: item.id || '',
  1034. }))
  1035. if (!list.length) {
  1036. uni.showToast({ title: '暂无指导书模板', icon: 'error' })
  1037. return
  1038. }
  1039. templateList.value = list
  1040. selectedTemplate.value = null
  1041. showSelectTemplatePopup.value = true
  1042. } catch (error) {
  1043. console.error('获取操作指导书模板失败:', error)
  1044. uni.showToast({ title: '获取操作指导书模板失败', icon: 'error' })
  1045. }
  1046. }
  1047. const closeSelectTemplatePopup = () => {
  1048. showSelectTemplatePopup.value = false
  1049. selectedTemplate.value = null
  1050. }
  1051. const onTemplateChange = (e: any) => {
  1052. const index = e.detail.value
  1053. selectedTemplate.value = templateList.value[index]
  1054. }
  1055. const handleConfirmTemplate = async () => {
  1056. if (!selectedTemplate.value) {
  1057. uni.showToast({ title: '请选择模板', icon: 'error' })
  1058. return
  1059. }
  1060. try {
  1061. uni.showLoading({ title: '添加中...' })
  1062. const params = {
  1063. orderId: props.orderId,
  1064. orderItemId: props.orderItemId,
  1065. templateId: selectedTemplate.value.value,
  1066. prepareId: userInfo.value?.id || '',
  1067. prepareName: userInfo.value?.nickname || '',
  1068. }
  1069. const result = await addBoilerMajorIssues(params)
  1070. if (result.code !== 0) {
  1071. uni.hideLoading()
  1072. uni.showToast({ title: result?.msg || '添加失败', icon: 'error' })
  1073. return
  1074. }
  1075. const newReportId = result?.data || ''
  1076. if (!newReportId) {
  1077. uni.hideLoading()
  1078. uni.showToast({ title: '添加失败', icon: 'error' })
  1079. return
  1080. }
  1081. uni.navigateTo({
  1082. url: `/pages/editor/workInstructionEditor?templateId=${selectedTemplate.value.value}&refId=${newReportId}`,
  1083. })
  1084. } catch (error) {
  1085. uni.hideLoading()
  1086. console.error('添加指导书失败:', error)
  1087. uni.showToast({ title: '添加指导书失败', icon: 'error' })
  1088. } finally {
  1089. uni.hideLoading()
  1090. closeSelectTemplatePopup()
  1091. props.refreshDetail?.()
  1092. }
  1093. }
  1094. const showCheckProjectPopup = () => {
  1095. props.showCheckProjectPopupFn?.()
  1096. }
  1097. const handleCheckReport = async () => {
  1098. if (selectedProjects.value.length === 0) {
  1099. uni.showToast({ title: '请选择要上传的项目', icon: 'error' })
  1100. return
  1101. }
  1102. try {
  1103. const itemArray = selectedProjects.value
  1104. .map((id) => props.reportList.find((i) => i.id === id))
  1105. .filter(Boolean)
  1106. for (const checkItem of itemArray) {
  1107. const userId =
  1108. checkItem.reportType === PressureReportType.MAIN
  1109. ? props.equipment?.mainCheckerUser?.id
  1110. : checkItem?.checkUsers?.[0]?.id
  1111. if (userId && userId !== userInfo.value?.id) {
  1112. uni.showToast({ title: '已分配项目不支持上传', icon: 'error' })
  1113. return
  1114. }
  1115. }
  1116. const { checkedCheckItemhasSubmited } = await import('@/api/task')
  1117. const promiseArr = itemArray.map((item) => checkedCheckItemhasSubmited({ id: item.id }))
  1118. const result = await Promise.all(promiseArr)
  1119. const checkNameArr = []
  1120. for (const res of result) {
  1121. if (res?.code === 0 && res?.data?.isSubmit) {
  1122. checkNameArr.push(res?.data.checkName)
  1123. }
  1124. }
  1125. if (checkNameArr?.length) {
  1126. tipsPopupRef.value?.show({
  1127. text: `检验员:${checkNameArr.join('、')}已经提交过,确定要覆盖吗?`,
  1128. confirm: handleBatchUpload,
  1129. })
  1130. } else {
  1131. handleBatchUpload()
  1132. }
  1133. } catch (error) {
  1134. console.error('批量上传失败:', error)
  1135. uni.showToast({ title: '项目检验失败', icon: 'error' })
  1136. }
  1137. }
  1138. const handleBatchUpload = async () => {
  1139. try {
  1140. uni.showLoading({ title: '上传中...' })
  1141. const itemArray = selectedProjects.value
  1142. .map((id) => props.reportList.find((i) => i.id === id))
  1143. .filter(Boolean)
  1144. const params: any = { addList: [], updateList: [] }
  1145. itemArray.forEach((item) => {
  1146. const dic = {
  1147. orderId: props.orderId,
  1148. orderItemId: props.orderItemId,
  1149. reportUrl: item.reportUrl || '',
  1150. dataJson: '',
  1151. prepareUrl: '',
  1152. prepareJson: item.prepareJson || '',
  1153. modifiedReason: '',
  1154. templateId: item.templateId,
  1155. reportType: item.reportType,
  1156. equipAddress: props.equipment?.equipAddress || '',
  1157. equipLatitude: props.equipment?.equipLatitude || '',
  1158. equipLongitude: props.equipment?.equipLongitude || '',
  1159. fee: item?.fee || 0,
  1160. }
  1161. if (item.isLocal) {
  1162. dic.localId = item.id
  1163. params.addList.push(dic)
  1164. } else {
  1165. dic.id = item.id
  1166. params.updateList.push(dic)
  1167. }
  1168. })
  1169. const { postCheckItemRecordEnter } = await import('@/api/task')
  1170. const postResult = await postCheckItemRecordEnter(params)
  1171. uni.hideLoading()
  1172. if (postResult.data) {
  1173. uni.showToast({ title: '提交成功', icon: 'success' })
  1174. props.refreshDetail?.()
  1175. initSelected()
  1176. } else {
  1177. uni.showToast({ title: postResult?.msg || '提交失败', icon: 'error' })
  1178. }
  1179. } catch (error) {
  1180. uni.hideLoading()
  1181. console.error('批量上传失败:', error)
  1182. uni.showToast({ title: '批量上传失败', icon: 'error' })
  1183. }
  1184. }
  1185. const handleDelReport = () => {
  1186. if (selectedProjects.value.length === 0) {
  1187. uni.showToast({ title: '请选择要作废的项目', icon: 'error' })
  1188. return
  1189. }
  1190. // 主检人可以作废所有项目(未提交校核的项目,且不包括主项目),检验人只能作废自己的项目
  1191. // 目前在 equipmentDetail 中会过滤掉不可见的报告,所以不需要根据身份检查作废项目的权限
  1192. const hasMain = selectedProjects.value.some((id) => {
  1193. const item = props.reportList.find((i) => i.id === id)
  1194. return item?.reportType === PressureReportType.MAIN
  1195. })
  1196. if (hasMain) {
  1197. uni.showToast({ title: '主报告不能作废', icon: 'error' })
  1198. return
  1199. }
  1200. const hasReportEnd =
  1201. props.useOnline === '1' &&
  1202. selectedProjects.value.some((id) => {
  1203. const item = props.reportList.find((i) => i.id === id)
  1204. return item?.taskStatus >= PressureCheckerMyTaskStatus.RECORD_CHECK
  1205. })
  1206. if (hasReportEnd) {
  1207. uni.showToast({ title: '已提交校核的项目不能作废', icon: 'error' })
  1208. return
  1209. }
  1210. tipsPopupRef.value?.show({
  1211. text: '是否作废已选择的检验项目?',
  1212. showInput: true,
  1213. inputPlaceholder: '请输入作废理由',
  1214. confirm: async (reason?: string) => {
  1215. try {
  1216. uni.showLoading({ title: '作废中...' })
  1217. const cancelIds = selectedProjects.value
  1218. let result = null
  1219. if (props.useOnline === '1') {
  1220. const cancelReq = []
  1221. cancelIds.forEach((id) => {
  1222. cancelReq.push({ id, reason: reason || '' })
  1223. })
  1224. const fetchResult = await cancelBoilerInSpectProject(cancelReq)
  1225. result = fetchResult.data
  1226. } else {
  1227. uni.showToast({ title: '离线模式作废功能开发中', icon: 'none' })
  1228. uni.hideLoading()
  1229. return
  1230. }
  1231. uni.hideLoading()
  1232. if (result) {
  1233. props.refreshDetail?.()
  1234. initSelected()
  1235. uni.showToast({ title: '作废成功', icon: 'success' })
  1236. } else {
  1237. uni.showToast({ title: '作废失败', icon: 'error' })
  1238. }
  1239. } catch (error) {
  1240. uni.hideLoading()
  1241. console.error('作废失败:', error)
  1242. uni.showToast({ title: '作废失败', icon: 'error' })
  1243. }
  1244. },
  1245. })
  1246. }
  1247. const uploadFileAndSubmitCheckItemCallback = {
  1248. callback: async (item: CheckItem) => {
  1249. const userId =
  1250. item.reportType === PressureReportType.MAIN
  1251. ? props.equipment?.mainCheckerUser?.id
  1252. : item?.checkUsers?.[0]?.id
  1253. if (userId && userId !== userInfo.value?.id) {
  1254. uni.showToast({ title: '已分配项目不支持上传', icon: 'error' })
  1255. return
  1256. }
  1257. try {
  1258. uni.showLoading({ title: '上传中...' })
  1259. const params = {
  1260. orderId: props.orderId,
  1261. orderItemId: props.orderItemId,
  1262. reportUrl: item.reportUrl || '',
  1263. dataJson: '',
  1264. prepareUrl: '',
  1265. prepareJson: item.prepareJson || '',
  1266. modifiedReason: '',
  1267. templateId: item.templateId,
  1268. reportType: item.reportType,
  1269. fee: item?.fee || 0,
  1270. }
  1271. const newParams: any = {}
  1272. if (item.isLocal) {
  1273. params.localId = item.id
  1274. newParams.addList = [params]
  1275. } else {
  1276. params.id = item.id
  1277. newParams.updateList = [params]
  1278. }
  1279. const { postCheckItemRecordEnter } = await import('@/api/task')
  1280. const postResult = await postCheckItemRecordEnter(newParams)
  1281. uni.hideLoading()
  1282. if (postResult.data) {
  1283. uni.showToast({ title: '提交成功', icon: 'success' })
  1284. props.refreshDetail?.()
  1285. } else {
  1286. uni.showToast({ title: postResult?.msg || '提交失败', icon: 'error' })
  1287. }
  1288. } catch (error) {
  1289. uni.hideLoading()
  1290. console.error('上传失败:', error)
  1291. uni.showToast({ title: '上传失败', icon: 'error' })
  1292. }
  1293. },
  1294. }
  1295. </script>
  1296. <style lang="scss" scoped>
  1297. .inspect-project-container {
  1298. display: flex;
  1299. flex-direction: column;
  1300. height: 100%;
  1301. background-color: #f5f5f5;
  1302. }
  1303. .scroll-list {
  1304. flex: 1;
  1305. padding: 12px;
  1306. padding-bottom: 80px;
  1307. }
  1308. .report-list {
  1309. display: flex;
  1310. flex-direction: column;
  1311. }
  1312. .report-item {
  1313. padding: 15px;
  1314. margin-bottom: 12px;
  1315. background-color: #fff;
  1316. border-radius: 5px;
  1317. }
  1318. .part-card {
  1319. display: flex;
  1320. flex-flow: column nowrap;
  1321. margin: 0 0 12px 0;
  1322. background-color: rgb(245, 245, 245);
  1323. border-color: gray;
  1324. border-style: solid;
  1325. border-width: 0px;
  1326. border-radius: 8px;
  1327. .part-card-header {
  1328. display: flex;
  1329. flex-flow: row nowrap;
  1330. align-items: center;
  1331. justify-content: space-between;
  1332. height: 30px;
  1333. font-size: 16px;
  1334. font-weight: 500;
  1335. background-color: white;
  1336. border-color: gray;
  1337. border-style: solid;
  1338. border-width: 0 0 1px 0;
  1339. .part-card-header-text {
  1340. margin: 8px;
  1341. }
  1342. .part-card-header-btn-group {
  1343. display: flex;
  1344. flex-flow: row nowrap;
  1345. justify-content: flex-end;
  1346. view {
  1347. margin: 4px;
  1348. color: rgb(47, 142, 255);
  1349. }
  1350. }
  1351. }
  1352. .report-item {
  1353. padding: 12px 4%;
  1354. margin: 0 0 8px 0;
  1355. }
  1356. }
  1357. .item-top {
  1358. display: flex;
  1359. flex-direction: row;
  1360. align-items: center;
  1361. justify-content: space-between;
  1362. padding-bottom: 10px;
  1363. border-bottom: 1px solid rgb(244, 244, 244);
  1364. }
  1365. .top-left {
  1366. display: flex;
  1367. flex: 1;
  1368. flex-direction: row;
  1369. align-items: center;
  1370. }
  1371. .report-name {
  1372. margin-left: 8px;
  1373. font-size: 15px;
  1374. color: rgb(51, 51, 51);
  1375. }
  1376. .type-tag {
  1377. display: flex;
  1378. align-items: center;
  1379. justify-content: center;
  1380. width: 20px;
  1381. height: 20px;
  1382. margin-left: 10px;
  1383. border-radius: 4px;
  1384. }
  1385. .type-tag-text {
  1386. font-size: 12px;
  1387. color: rgb(252, 255, 253);
  1388. }
  1389. .status-text {
  1390. font-size: 15px;
  1391. }
  1392. .item-center {
  1393. margin-top: 10px;
  1394. }
  1395. .center-row {
  1396. display: flex;
  1397. flex-direction: row;
  1398. align-items: center;
  1399. margin-bottom: 10px;
  1400. }
  1401. .label {
  1402. width: 80px;
  1403. font-size: 15px;
  1404. color: rgb(51, 51, 51);
  1405. }
  1406. .conclusion-box {
  1407. min-width: 80px;
  1408. padding: 10px;
  1409. background-color: rgb(243, 245, 247);
  1410. border: 1px solid rgb(187, 187, 187);
  1411. border-radius: 4px;
  1412. }
  1413. .conclusion-error {
  1414. background-color: #fff2f2;
  1415. border-color: #ff3535;
  1416. }
  1417. .conclusion-text {
  1418. font-size: 13px;
  1419. color: #4b8cd9;
  1420. }
  1421. .conclusion-error-text {
  1422. color: #ff3535;
  1423. }
  1424. .result-box {
  1425. flex: 1;
  1426. padding: 10px;
  1427. background-color: rgb(243, 245, 247);
  1428. border: 1px solid rgb(187, 187, 187);
  1429. border-radius: 4px;
  1430. }
  1431. .result-text {
  1432. font-size: 13px;
  1433. }
  1434. .right-area {
  1435. display: flex;
  1436. flex: 1;
  1437. flex-direction: row;
  1438. flex-wrap: wrap;
  1439. align-items: center;
  1440. justify-content: flex-end;
  1441. }
  1442. .instruction-tag {
  1443. padding: 4px 8px;
  1444. margin-right: 8px;
  1445. background-color: #f2f6ff;
  1446. border: 1px solid #4b8cd9;
  1447. border-radius: 4px;
  1448. }
  1449. .instruction-tag-error {
  1450. background-color: #fff2f2;
  1451. border-color: #ff3535;
  1452. }
  1453. .instruction-text {
  1454. font-size: 12px;
  1455. color: #4b8cd9;
  1456. }
  1457. .instruction-text-error {
  1458. color: #ff3535;
  1459. }
  1460. .inspector-name {
  1461. font-size: 15px;
  1462. color: rgb(51, 51, 51);
  1463. }
  1464. .inspector-btn {
  1465. display: flex;
  1466. flex-direction: row;
  1467. align-items: center;
  1468. }
  1469. .inspector-label {
  1470. font-size: 15px;
  1471. color: rgb(51, 51, 51);
  1472. }
  1473. .inspector-value {
  1474. font-size: 15px;
  1475. color: rgb(75, 140, 217);
  1476. }
  1477. .fee-area {
  1478. display: flex;
  1479. flex: 1;
  1480. flex-direction: row;
  1481. align-items: center;
  1482. justify-content: space-between;
  1483. }
  1484. .fee-btns {
  1485. display: flex;
  1486. flex-direction: row;
  1487. align-items: center;
  1488. }
  1489. .fee-value-box {
  1490. padding: 4px 8px;
  1491. margin-right: 3px;
  1492. background-color: #f3f5f7;
  1493. border: 1px solid rgb(182, 216, 236);
  1494. border-radius: 4px;
  1495. }
  1496. .fee-value {
  1497. font-size: 12px;
  1498. color: rgb(75, 140, 217);
  1499. }
  1500. .fee-none {
  1501. font-size: 12px;
  1502. color: rgb(75, 140, 217);
  1503. }
  1504. .fee-btn {
  1505. padding: 4px 8px;
  1506. background-color: rgb(47, 142, 255);
  1507. border: 1px solid rgb(47, 142, 255);
  1508. border-radius: 4px;
  1509. }
  1510. .fee-btn-text {
  1511. font-size: 12px;
  1512. color: rgb(222, 238, 255);
  1513. }
  1514. .action-btns {
  1515. display: flex;
  1516. flex: 1;
  1517. flex-direction: row;
  1518. flex-wrap: wrap;
  1519. justify-content: flex-end;
  1520. }
  1521. .action-btn {
  1522. display: flex;
  1523. align-items: center;
  1524. justify-content: center;
  1525. height: 29px;
  1526. padding: 0 5px;
  1527. margin-left: 3px;
  1528. background-color: #f3f5f7;
  1529. border: 1px solid rgb(182, 216, 236);
  1530. border-radius: 4px;
  1531. }
  1532. .action-btn-text {
  1533. font-size: 12px;
  1534. color: rgb(75, 140, 217);
  1535. }
  1536. .sign-btn {
  1537. background-color: rgba(78, 185, 95, 1);
  1538. border-color: rgba(78, 185, 95, 1);
  1539. }
  1540. .sign-btn .action-btn-text {
  1541. color: rgb(222, 238, 255);
  1542. }
  1543. .attachment-btn {
  1544. background-color: rgb(230, 162, 60);
  1545. border-color: rgb(230, 162, 60);
  1546. }
  1547. .attachment-btn .action-btn-text {
  1548. color: rgb(222, 238, 255);
  1549. }
  1550. .record-btn,
  1551. .re-input-btn {
  1552. background-color: #f3f5f7;
  1553. }
  1554. .submit-check-btn,
  1555. .submit-report-btn {
  1556. margin-right: 0;
  1557. background-color: rgb(47, 142, 255);
  1558. border-color: rgb(47, 142, 255);
  1559. }
  1560. .submit-check-btn .action-btn-text,
  1561. .submit-report-btn .action-btn-text {
  1562. color: rgb(222, 238, 255);
  1563. }
  1564. .empty-state {
  1565. display: flex;
  1566. align-items: center;
  1567. justify-content: center;
  1568. padding: 40px 0;
  1569. }
  1570. .empty-text {
  1571. font-size: 14px;
  1572. color: #999;
  1573. }
  1574. .bottom-bar {
  1575. position: fixed;
  1576. right: 0;
  1577. bottom: 0;
  1578. left: 0;
  1579. z-index: 100;
  1580. display: flex;
  1581. flex-direction: row;
  1582. align-items: center;
  1583. justify-content: space-between;
  1584. height: 68px;
  1585. padding: 0 12px;
  1586. background-color: #fff;
  1587. border-top: 1px solid rgb(244, 244, 244);
  1588. }
  1589. .select-all-box {
  1590. display: flex;
  1591. flex: 1;
  1592. flex-direction: row;
  1593. align-items: center;
  1594. }
  1595. .square-checkbox {
  1596. border-radius: 0 !important;
  1597. transform: scale(0.9);
  1598. }
  1599. .select-all-text {
  1600. margin-left: 10px;
  1601. font-size: 15px;
  1602. line-height: 1.4;
  1603. color: rgb(51, 51, 51);
  1604. white-space: pre-line;
  1605. }
  1606. .action-buttons {
  1607. display: flex;
  1608. flex: 2;
  1609. flex-direction: row;
  1610. align-items: center;
  1611. justify-content: flex-end;
  1612. }
  1613. .operate-btn {
  1614. display: flex;
  1615. align-items: center;
  1616. justify-content: center;
  1617. min-width: 66px;
  1618. height: 40px;
  1619. padding: 0 5px;
  1620. margin-left: 5px;
  1621. border-radius: 4px;
  1622. }
  1623. .operate-btn-text {
  1624. font-size: 14px;
  1625. color: rgb(251, 253, 255);
  1626. }
  1627. .delete-btn {
  1628. background-color: #ff4445;
  1629. }
  1630. .guide-btn {
  1631. background-color: #e6a23c;
  1632. }
  1633. .part-btn {
  1634. background-color: #e6a23c;
  1635. }
  1636. .add-btn {
  1637. background-color: #e6a23c;
  1638. }
  1639. .upload-btn {
  1640. background-color: rgb(47, 142, 255);
  1641. }
  1642. .popup-overlay {
  1643. position: fixed;
  1644. top: 0;
  1645. right: 0;
  1646. bottom: 0;
  1647. left: 0;
  1648. z-index: 999;
  1649. display: flex;
  1650. align-items: center;
  1651. justify-content: center;
  1652. background-color: rgba(0, 0, 0, 0.5);
  1653. }
  1654. .popup-content {
  1655. width: 80%;
  1656. max-width: 300px;
  1657. padding: 20px;
  1658. background-color: #fff;
  1659. border-radius: 8px;
  1660. }
  1661. .popup-title {
  1662. display: block;
  1663. margin-bottom: 20px;
  1664. font-size: 18px;
  1665. font-weight: bold;
  1666. color: #333;
  1667. text-align: center;
  1668. }
  1669. .picker-value {
  1670. padding: 10px;
  1671. margin-bottom: 20px;
  1672. border: 1px solid #ddd;
  1673. border-radius: 4px;
  1674. }
  1675. .popup-actions {
  1676. display: flex;
  1677. justify-content: space-between;
  1678. }
  1679. .popup-actions .action-btn {
  1680. flex: 1;
  1681. padding: 10px 0;
  1682. margin: 0 5px;
  1683. font-size: 14px;
  1684. text-align: center;
  1685. border-radius: 4px;
  1686. }
  1687. .popup-actions .cancel-btn {
  1688. color: #666;
  1689. background-color: #f5f5f5;
  1690. }
  1691. .popup-actions .confirm-btn {
  1692. color: #fff;
  1693. background-color: #007aff;
  1694. }
  1695. </style>