StatusOperationPanel.vue 114 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649
  1. <template>
  2. <div class="container-panel">
  3. <el-row class="info-panel">
  4. <div class="info-item">
  5. <span class="label">报告编号:</span>
  6. <span class="value">{{ selectedItem?.reportNo || '-' }}</span>
  7. </div>
  8. <div class="info-item" style="margin: 0 auto;" v-if="isShowRecordOrReportBtn">
  9. <!-- <el-switch-->
  10. <!-- v-model="isShowReportPdf"-->
  11. <!-- active-text="报告"-->
  12. <!-- inactive-text="记录"-->
  13. <!-- @change="handleChangeShowReportPdf"-->
  14. <!-- />-->
  15. <!-- <el-radio-group v-model="showReportPdfType" @change="handleChangeShowReportPdf">-->
  16. <!-- <el-radio-button label="record">记录</el-radio-button>-->
  17. <!-- <el-radio-button label="report">报告</el-radio-button>-->
  18. <!-- <el-radio-button label="result" v-if="selectedItem?.reportType === 100">结论报告</el-radio-button>-->
  19. <!-- </el-radio-group>-->
  20. <div class="capsule-tabs">
  21. <div
  22. class="tab-item"
  23. :class="{ active: showReportPdfType === 'record' }"
  24. @click="handleChangeShowReportPdf('record')"
  25. v-if="hasRecord"
  26. >
  27. <span>记录</span>
  28. </div>
  29. <div
  30. class="tab-item"
  31. :class="{ active: showReportPdfType === 'report' }"
  32. @click="handleChangeShowReportPdf('report')"
  33. >
  34. <span>报告</span>
  35. </div>
  36. </div>
  37. </div>
  38. <div class="info-item" style="margin: 0 auto;">
  39. <span class="label">检验项目:</span>
  40. <span class="value" :title="selectedItem?.reportName || '-'">{{
  41. selectedItem?.reportName || '-'
  42. }}</span>
  43. </div>
  44. <div v-if="selectedItem?.reportType === 300">
  45. <span>{{ selectedItem.instructionId ? '已' : '未'}}关联操作指导书</span>
  46. <el-button
  47. type="primary"
  48. size="small"
  49. link
  50. class="edit-checker-btn"
  51. @click="handleShowAssociationOperationManual"
  52. >
  53. <Icon icon="ep:edit" />
  54. </el-button>
  55. </div>
  56. <div class="info-item" style="flex-basis: 300px; text-align: end;">
  57. <span class="label">{{ selectedItem?.reportType === 100 ? '主检人' : '检验员' }}:</span>
  58. <span
  59. class="value"
  60. :title="
  61. selectedItem?.reportType === 100
  62. ? taskOrderItem?.mainCheckerUser?.nickname || '-'
  63. : getCheckersName()
  64. "
  65. >{{
  66. selectedItem?.reportType === 100
  67. ? taskOrderItem?.mainCheckerUser?.nickname || '-'
  68. : getCheckersName()
  69. }}</span
  70. >
  71. <el-button
  72. v-if="!isAuditMode"
  73. type="primary"
  74. size="small"
  75. link
  76. class="edit-checker-btn"
  77. @click="
  78. () =>
  79. selectedItem?.reportType === 100 ? handleModifyMainChecker() : handleModifyChecker()
  80. "
  81. :disabled="(selectedItem?.reportType === 100 && taskOrderItem?.mainCheckerUser?.id !== userStore?.user?.id)
  82. || (selectedItem?.checkUsers?.length > 0 && selectedItem?.checkUsers?.[0]?.id !== userStore?.user?.id && taskOrderItem?.mainCheckerUser?.id !== userStore?.user?.id)
  83. || isCompleteInput"
  84. >
  85. <!-- 主报告:登录用户为主检人的时,才可以修改主检人 -->
  86. <!-- 子报告:登录用户为检验员或主检人的时候,才可以修改检验员 -->
  87. <Icon icon="ep:edit" />
  88. </el-button>
  89. </div>
  90. </el-row>
  91. <el-row class="status-operation-panel">
  92. <!-- 未选择任何项目时的提示 -->
  93. <div v-if="!selectedItem" class="no-selection">
  94. <el-empty description="请选择一个检验项目查看详情" :image-size="100" />
  95. </div>
  96. <!-- 选择单个项目时显示详情操作 -->
  97. <div v-else class="single-item-panel">
  98. <!--
  99. 1、重大问题线索告知表
  100. 2、作业指导书
  101. 3、检验方案
  102. 以上报告类型不显示做进度栏目
  103. -->
  104. <InspectionItemProgress
  105. v-if="!onlyShowPdf"
  106. :selected-item="selectedItem"
  107. :task-info="taskInfo"
  108. :is-audit-mode="isAuditMode"
  109. @modify-checker="handleModifyChecker"
  110. />
  111. <!-- <div class="pdf-panel" :style="{ maxWidth: !onlyShowPdf ? '1200px' : 'unset' }"> -->
  112. <div ref="pdfPanelRef" class="pdf-panel" >
  113. <!-- PDF预览区域 -->
  114. <div class="!h-full" :style="{ width: pdfContentWidth + 'px'}">
  115. <SpreadViewer :initData="initData" ref="spreadRef" isFullscreen @saveSuccess="saveSuccess"/>
  116. </div>
  117. </div>
  118. <!--
  119. 1、重大问题线索告知表
  120. 2、作业指导书
  121. 3、检验方案
  122. 以上报告类型不显示右侧栏目
  123. -->
  124. <template v-if="showCheckBook && getReportStatusEnd">
  125. <div class="right-panel-container">
  126. <!-- 收缩展开按钮 -->
  127. <div class="toggle-btn" @click="togglePanel" :class="{ 'collapsed': !isExpanded }">
  128. <el-icon>
  129. <Back v-if="!isExpanded" />
  130. <Right v-else />
  131. </el-icon>
  132. </div>
  133. <div class="operation-panel" :class="{ 'expanded': isExpanded, 'collapsed': !isExpanded }">
  134. <div class="operation-inner custom-inner">
  135. <template v-if="checkBookDetail.rectificationStatus === 2">
  136. <div class="operation-item">
  137. <div class="item-header"> 回退原因 </div>
  138. <div class="item-content">
  139. <el-form
  140. ref="returnFormRef"
  141. :model="returnForm"
  142. :rules="returnFormRules"
  143. label-position="right"
  144. label-width="80px"
  145. >
  146. <el-form-item label="回退原因" prop="rejectionReason">
  147. <el-input
  148. v-model="returnForm.rejectionReason"
  149. type="textarea"
  150. :rows="5"
  151. maxlength="100"
  152. placeholder="请输入回退原因"
  153. />
  154. </el-form-item>
  155. </el-form>
  156. </div>
  157. </div>
  158. </template>
  159. <div class="operation-item1">
  160. <div class="item-header"> 处理人信息 </div>
  161. <div class="item-content">
  162. <div class="item-content-item">
  163. <span class="item-content-item-label">联系人姓名:</span>
  164. <span class="item-content-item-value">{{ checkBookDetail?.recipient || '-' }}</span>
  165. </div>
  166. <div class="item-content-item">
  167. <span class="item-content-item-label">联系人电话:</span>
  168. <span class="item-content-item-value">{{ checkBookDetail?.recipientPhone || '-'}}</span>
  169. </div>
  170. <div class="item-content-item">
  171. <span class="item-content-item-label">当前状态:</span>
  172. <span class="item-content-item-value">{{
  173. rectificationStatusMap[checkBookDetail?.rectificationStatus] || '-'
  174. }}</span>
  175. </div>
  176. </div>
  177. </div>
  178. <div class="operation-item1 videoAndImg">
  179. <div class="item-header"> 整改材料 </div>
  180. <div class="item-content1">
  181. <div class="item-content1-item">图片:</div>
  182. <div class="item-content1-img">
  183. <template
  184. v-for="(path, index) in checkBookDetail.rectificationImage?.split(',') || []"
  185. :key="'img' + index"
  186. >
  187. <div class="item-content1-img-item">
  188. <el-image
  189. class="img-item"
  190. :src="buildFileUrl(path)"
  191. :preview-src-list="[buildFileUrl(path)]"
  192. fit="cover"
  193. preview-teleported
  194. />
  195. </div>
  196. </template>
  197. </div>
  198. </div>
  199. <div class="item-content1">
  200. <div class="item-content1-item">视频:</div>
  201. <div class="item-content1-img">
  202. <template
  203. v-for="(path, index) in checkBookDetail.rectificationVideo?.split(',') || []"
  204. :key="'vid' + index"
  205. >
  206. <div class="item-content1-img-item" @click="handlePreview(buildFileUrl(path), 'video')">
  207. <video class="img-item" :src="buildFileUrl(path)"></video>
  208. <div class="video-play-overlay">
  209. <el-icon :size="28"><VideoPlay /></el-icon>
  210. </div>
  211. </div>
  212. </template>
  213. </div>
  214. </div>
  215. </div>
  216. </div>
  217. </div>
  218. </div>
  219. </template>
  220. <template v-else>
  221. <div class="right-panel-container">
  222. <!-- 收缩展开按钮 -->
  223. <div class="toggle-btn" @click="togglePanel" :class="{ 'collapsed': !isExpanded }" v-if="!onlyShowPdf">
  224. <el-icon>
  225. <Back v-if="!isExpanded" />
  226. <Right v-else />
  227. </el-icon>
  228. </div>
  229. <div class="operation-panel" :class="{ 'expanded': isExpanded, 'collapsed': !isExpanded }" v-if="!onlyShowPdf">
  230. <div class="operation-inner">
  231. <!-- 流转记录 -->
  232. <div class="operation-item">
  233. <div class="item-header"> 流转记录 </div>
  234. <div class="item-content">
  235. <el-empty
  236. v-if="!recordList.length"
  237. description="暂无流转记录"
  238. :image-size="120"
  239. />
  240. <div class="record-item" v-for="record in recordList" :key="record.id">
  241. <div class="record-item-title">{{ PressureReportType['SUGGUESTION'] === props.selectedItem?.reportType ? record.processName : PressureCheckerMyTaskStatusMap[record.process]
  242. }}</div>
  243. <div class="record-item-inner">
  244. <div class="content">
  245. <span>{{ record.createUser?.nickname }}</span>
  246. <el-button
  247. :type="
  248. OAApprovalResultType[record.result] ||
  249. (record.result === 100 || record.result === '100'
  250. ? 'success'
  251. : record.result === 200 || record.result === '200'
  252. ? 'danger'
  253. : 'default')
  254. "
  255. round
  256. size="small"
  257. >{{
  258. OAApprovalResultMap[record.result] ||
  259. (record.result === 100 || record.result === '100'
  260. ? '通过'
  261. : record.result === 200 || record.result === '200'
  262. ? '拒绝'
  263. : record.result || '-')
  264. }}</el-button
  265. >
  266. <span class="time">{{
  267. !record.createTime
  268. ? '-'
  269. : dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss')
  270. }}</span>
  271. </div>
  272. <p class="desc">
  273. <span>描述</span>
  274. {{ record.remark && record.remark.trim() !== '' ? record.remark : '-' }}
  275. </p>
  276. </div>
  277. </div>
  278. </div>
  279. </div>
  280. <!-- 智能纠错 -->
  281. <div class="operation-item">
  282. <div class="item-header"> 智能纠错 </div>
  283. <div class="item-content">
  284. <el-empty
  285. v-if="!checkInputList.length"
  286. description="暂无纠错内容"
  287. :image-size="120"
  288. />
  289. <div class="error-item" v-for="input in checkInputList" :key="input.code">
  290. <el-icon color="#E0534E" :size="20"><InfoFilled /></el-icon>
  291. {{ input.code }}:{{ input.name }}
  292. </div>
  293. </div>
  294. </div>
  295. <!-- 历史版本 -->
  296. <div class="operation-item">
  297. <div class="item-header"> 历史版本 </div>
  298. <div class="item-content" v-if="historyList.length">
  299. <!-- <template> -->
  300. <div class="history-item" v-for="item in historyList" :key="item.id">
  301. <div class="history-title">{{ item.versionNoStr || '-' }}号版本</div>
  302. <p
  303. >修改原因:<span>{{ item.modifiedReason || '人工修改' }}</span></p
  304. >
  305. <div class="history-footer">
  306. <span class="name">{{ item.creatorName || '-' }}</span>
  307. <span class="time"
  308. >修改于{{
  309. dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss') || '-'
  310. }}</span
  311. >
  312. </div>
  313. <div class="my-2">
  314. <el-button type="primary" link @click="handleShowVersionInfo(item)"
  315. >查看详情</el-button
  316. >
  317. <el-button v-if="!checkerIsLoginUser" type="primary" link @click="restoreVersion(item)"
  318. >恢复版本</el-button
  319. >
  320. </div>
  321. </div>
  322. <!-- </template> -->
  323. </div>
  324. <template v-else>
  325. <el-empty description="暂无历史版本" :image-size="120" />
  326. </template>
  327. </div>
  328. </div>
  329. </div>
  330. </div>
  331. </template>
  332. </div>
  333. <!-- 审批人选择对话框 -->
  334. <AuditUserDialog
  335. v-if="isShowApprovalDialog"
  336. v-model="isShowApprovalDialog"
  337. :apiFn="getRecheckUserList"
  338. title="请选择审批人"
  339. selectedAlertText="已选择审批人"
  340. width="700px"
  341. :columns="[
  342. {
  343. type: 'selection',
  344. fieldProps: {
  345. reserveSelection: true
  346. }
  347. },
  348. {
  349. label: '姓名',
  350. prop: 'nickname',
  351. search: {
  352. type: 'input',
  353. span: 12,
  354. prop: 'nickName'
  355. }
  356. },
  357. {
  358. label: '部门',
  359. prop: 'deptName',
  360. search: {
  361. type: 'input',
  362. span: 12
  363. }
  364. }
  365. ]"
  366. @confirm="handleApprovalSelectConfirm"
  367. />
  368. <Dialog
  369. v-model="isShowAuditDialog"
  370. width="400"
  371. :title="schemaFlag == 'proofread' ? '请选择校核人' : '请选择审核人'"
  372. >
  373. <CustomLargeListSelect
  374. v-model="form.recheckUser"
  375. :fetchFunc="getRecheckUserList"
  376. label-key="nickname"
  377. value-key="id"
  378. searchKeyProp="nickName"
  379. @change="handleChangeEntrustUnit"
  380. />
  381. <template #footer>
  382. <el-button type="primary" @click="handleAuditSelectConfirm" :loading="submitting">保 存</el-button>
  383. <el-button @click="isShowAuditDialog = false">取 消</el-button>
  384. </template>
  385. </Dialog>
  386. <!-- 批量提交校核 -->
  387. <Dialog
  388. v-model="showBatchSubmitToRecheckDialog"
  389. width="600"
  390. title="批量提交校核"
  391. >
  392. <el-form ref="batchRecheckFormRef" :model="batchRecheckForm" :rules="batchRecheckFormRules" label-width="80px">
  393. <el-form-item label="提交记录" prop="reportIds">
  394. <div class="batch-recheck-report-list">
  395. <!-- 查询框 -->
  396. <el-input
  397. v-model="batchRecheckSearchKey"
  398. placeholder="请输入检验项目名称查询"
  399. clearable
  400. size="small"
  401. class="batch-recheck-search"
  402. />
  403. <!-- 全选 + 当前数量 -->
  404. <div class="batch-recheck-header">
  405. <el-checkbox
  406. :model-value="batchRecheckCheckAll"
  407. :indeterminate="batchRecheckIndeterminate"
  408. @change="handleBatchRecheckCheckAll"
  409. >
  410. 全选
  411. </el-checkbox>
  412. <span class="batch-recheck-count">
  413. 已选 {{ batchRecheckForm.reportIds.length }} / {{ getCanSubmitRecheckReport.length }} 项
  414. </span>
  415. </div>
  416. <!-- 检验项目勾选列表 -->
  417. <div class="batch-recheck-checkbox-group">
  418. <el-checkbox
  419. v-for="item in filteredBatchRecheckReports"
  420. :key="item.id"
  421. :model-value="batchRecheckForm.reportIds.includes(item.id)"
  422. @change="(val) => handleBatchRecheckItemCheck(item.id, val)"
  423. class="batch-recheck-checkbox-item"
  424. >
  425. {{ getBatchRecheckReportDisplayName(item) }}
  426. </el-checkbox>
  427. <el-empty
  428. v-if="filteredBatchRecheckReports.length === 0"
  429. description="暂无匹配的检验项目"
  430. :image-size="60"
  431. />
  432. </div>
  433. </div>
  434. </el-form-item>
  435. <el-form-item label="校核人" prop="recheckUser">
  436. <CustomLargeListSelect
  437. v-model="batchRecheckForm.recheckUser"
  438. :fetchFunc="getRecheckUserList"
  439. label-key="nickname"
  440. value-key="id"
  441. searchKeyProp="nickName"
  442. @change="handleChangeEntrustUnit"
  443. placeholder="请选择校核人"
  444. />
  445. </el-form-item>
  446. <el-form-item label="">
  447. <span style="font-size: 13px; white-space: nowrap; margin-top: -16px; color: #999;">
  448. 您已完成{{getCanSubmitRecheckReport.length}}份记录录入,可以进行批量提交校核
  449. </span>
  450. </el-form-item>
  451. </el-form>
  452. <template #footer>
  453. <el-button type="primary" @click="handleBatchSubmitToRecheck">提 交</el-button>
  454. <el-button @click="showBatchSubmitToRecheckDialog = false">取 消</el-button>
  455. </template>
  456. </Dialog>
  457. <!-- 校核人选择对话框 暂时作废
  458. -->
  459. <!-- <AuditUserDialog
  460. v-if="isShowAuditDialog"
  461. v-model="isShowAuditDialog"
  462. :apiFn="getRecheckUserList"
  463. title="请选择校核人"
  464. selectedAlertText="已选择校核人"
  465. width="700px"
  466. :pageType="0"
  467. :columns="[
  468. {
  469. type: 'selection',
  470. fieldProps: {
  471. reserveSelection: true
  472. }
  473. },
  474. {
  475. label: '姓名',
  476. prop: 'nickname',
  477. search: {
  478. type: 'input',
  479. span: 12,
  480. prop: 'nickName'
  481. }
  482. },
  483. {
  484. label: '部门',
  485. prop: 'deptName',
  486. search: {
  487. type: 'input',
  488. span: 12
  489. }
  490. }
  491. ]"
  492. @confirm="handleAuditSelectConfirm"
  493. /> -->
  494. <!-- 报告审核人选择对话框 -->
  495. <Dialog
  496. v-model="isShowReportAuditDialog"
  497. width="400"
  498. title="请选择审核人"
  499. >
  500. <el-select
  501. v-model="reportAuditUserCode"
  502. filterable
  503. placeholder="请选择审核人"
  504. :loading="reportAuditUserLoading"
  505. clearable
  506. style="width: 100%"
  507. >
  508. <el-option
  509. v-for="item in reportAuditUserOptions"
  510. :key="item.code"
  511. :label="item.name"
  512. :value="item.code"
  513. />
  514. </el-select>
  515. <template #footer>
  516. <el-button type="primary" :loading="reportAuditSubmitting" @click="handleReportAuditSelectConfirm">确 定</el-button>
  517. <el-button @click="isShowReportAuditDialog = false">取 消</el-button>
  518. </template>
  519. </Dialog>
  520. </el-row>
  521. <!-- 检验结果录入-葡萄城模板 -->
  522. <InlineInspectionResultInput
  523. v-if="showInlineInspectionResultInput"
  524. v-model:visible="showInlineInspectionResultInput"
  525. :template-params="inspectionResultTemplateParams"
  526. @confirm="handleInspectionResultConfirm"
  527. @cancel="handleReturnToNormalMode"
  528. />
  529. <!-- 检验录入-模板 -->
  530. <!-- <InlineEditCheckRecord-->
  531. <!-- v-if="showInlineEditCheckRecord"-->
  532. <!-- :templateId="templateId"-->
  533. <!-- v-model:visible="showInlineEditCheckRecord"-->
  534. <!-- :report-list="reportList"-->
  535. <!-- :task-order-item="taskOrderItem"-->
  536. <!-- :template-params="templateParams"-->
  537. <!-- @confirm="handleTemplateConfirm"-->
  538. <!-- @cancel="handleRefresh"-->
  539. <!-- />-->
  540. <!-- 检验录入-模板 -->
  541. <CustomDialog v-model="showInlineEditCheckRecord" :show-footer="false" fullscreen :show-close="false">
  542. <SpreadViewer :initData="editData" ref="editSpreadRecordRef" @saveSuccess="saveSuccessRecord" @close="handleClose">
  543. <template #title>
  544. <div style="font-size: 20px; ">检验录入</div>
  545. </template>
  546. </SpreadViewer>
  547. </CustomDialog>
  548. <!-- 报告编制 - 模板 -->
  549. <!-- <InlineReportEdit-->
  550. <!-- v-if="showInlineReportEdit"-->
  551. <!-- v-model:visible="showInlineReportEdit"-->
  552. <!-- :template-params="templateParams"-->
  553. <!-- :report-list="reportList || []"-->
  554. <!-- :report-type="showReportPdfType"-->
  555. <!-- @confirm="handleTemplateConfirm"-->
  556. <!-- @cancel="handleRefresh"-->
  557. <!-- @refresh="()=>{refreshPdf = !refreshPdf}"-->
  558. <!-- />-->
  559. <!-- 报告编制 - 模板 -->
  560. <CustomDialog v-model="showInlineReportEdit" :show-footer="false" fullscreen :show-close="false">
  561. <SpreadViewer :initData="editData" ref="editSpreadReportRef" @saveSuccess="saveSuccessReport" @close="handleClose">
  562. <template #title>
  563. <div style="font-size: 20px; ">报告编制</div>
  564. </template>
  565. </SpreadViewer>
  566. </CustomDialog>
  567. <!-- 修改主检人弹窗 -->
  568. <UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelect" :single="true" />
  569. <CustomDialog v-model="showReportDialog" :show-footer="false" width="1000px">
  570. <!-- <VuePdfEmbed
  571. v-loading="pdfLoading"
  572. :height="500"
  573. :width="968"
  574. :source="reportSource"
  575. @rendered="handlePdfRendered"
  576. />-->
  577. <SpreadViewer :initData="reportInitData" ref="reportSpreadRef" />
  578. </CustomDialog>
  579. <!-- 查看版本详情的弹窗 -->
  580. <CustomDialog v-model="showVersionDetail" title="查看详情" :show-footer="false" width="1000px">
  581. <SmartTable
  582. v-model:columns="versionColumns"
  583. :buttons="versionDetailButtons"
  584. :data="versionDataCompareList"
  585. :is-pagination="false"
  586. :show-refresh="false"
  587. />
  588. </CustomDialog>
  589. <!-- 图片预览对话框 -->
  590. <el-dialog
  591. v-model="previewVisible"
  592. class="preview-dialog"
  593. title="Warning"
  594. width="500"
  595. :show-footer="false"
  596. :show-close="false"
  597. align-center
  598. >
  599. <div class="preview-container">
  600. <video class="preview-video" v-if="previewType === 'video'" :src="previewUrl" auto-play controls></video>
  601. <img class="preview-image" v-else :src="previewUrl" />
  602. </div>
  603. </el-dialog>
  604. <dialog ref="previewDialogRef"></dialog >
  605. <ReportListUploadModal
  606. ref="reportListUploadModalRef"
  607. :selectedItem="selectedItem"
  608. :reportList="reportList"
  609. @confirm="handleRefresh"
  610. />
  611. <!-- 回退原因弹窗(面板收缩时使用) -->
  612. <el-dialog
  613. v-model="rejectionReasonDialogVisible"
  614. title="请输入回退原因"
  615. width="450px"
  616. :close-on-click-modal="false"
  617. >
  618. <el-form ref="rejectionReasonDialogFormRef" :model="tempRejectionReason" :rules="returnFormRules">
  619. <el-form-item label="回退原因" prop="rejectionReason">
  620. <el-input
  621. v-model="tempRejectionReason.rejectionReason"
  622. type="textarea"
  623. :rows="5"
  624. maxlength="100"
  625. placeholder="请输入回退原因"
  626. />
  627. </el-form-item>
  628. </el-form>
  629. <template #footer>
  630. <el-button @click="rejectionReasonDialogVisible = false">取消</el-button>
  631. <el-button type="primary" @click="handleRejectionReasonDialogConfirm">确定回退</el-button>
  632. </template>
  633. </el-dialog>
  634. </div>
  635. <!-- 新加 -->
  636. <Teleport v-if="teleportBtnRef" to=".teleport-btn">
  637. <template v-if="showCheckBook && getReportStatusEnd">
  638. <div v-if="[0, 2].includes(checkBookDetail.rectificationStatus)" class="default-toolbar">
  639. <el-button type="success" size="small" @click="() => handlePass(0)">整改通过</el-button>
  640. <el-button type="success" size="small" @click="() => handlePass(2)">整改不通过</el-button>
  641. <el-button type="primary" size="small" @click="handleRectificationMaterials">上传整改材料</el-button>
  642. <template v-if="checkBookDetail.rectificationStatus === 2">
  643. <el-button type="primary" size="small" @click="() => handlePass(1)">回退</el-button>
  644. </template>
  645. </div>
  646. <div v-if="[3].includes(checkBookDetail.rectificationStatus)" class="default-toolbar">
  647. <el-button type="primary" size="small" @click="handleRectificationMaterials">上传整改材料</el-button>
  648. </div>
  649. </template>
  650. <template v-else>
  651. <div class="operation-btns">
  652. <!-- 报告办结后,这部分按钮不再显示 -->
  653. <template v-if="!getReportStatusEnd && selectedItem">
  654. <!-- 根据报告类型和配置显示相应按钮 -->
  655. <template
  656. v-if="selectedItem.reportType === PressureReportType.SINGLE"
  657. >
  658. <!-- 独审报告根据配置显示按钮 -->
  659. <el-button
  660. v-if="isCanSubmitToAudit"
  661. type="danger"
  662. @click="handleSubmitAudit"
  663. :disabled="checkerIsLoginUser"
  664. size="small"
  665. >
  666. 提交报告审核
  667. </el-button>
  668. <!-- 没有审核只有审批 -->
  669. <!-- <el-button
  670. v-if="
  671. currentReportConfig?.isRatify &&
  672. !currentReportConfig?.isApproval &&
  673. isCanSubmitToAudit
  674. "
  675. type="danger"
  676. @click="handleApprovalSelectConfirm"
  677. :disabled="checkerIsLoginUser"
  678. size="small"
  679. >
  680. 提交报告审批
  681. </el-button> -->
  682. </template>
  683. <!-- 非独审报告默认显示审核按钮 -->
  684. <el-button
  685. v-if="
  686. selectedItem.reportType !== PressureReportType.SUB &&
  687. selectedItem.reportType !== PressureReportType.SINGLE &&
  688. isCanSubmitToAudit &&
  689. 'BoilerCheckerTaskDetail' === routeName
  690. "
  691. type="danger"
  692. @click="handleSubmitAudit"
  693. :disabled="checkerIsLoginUser"
  694. size="small"
  695. >
  696. {{ selectedItem.reportType === PressureReportType['SUGGUESTION'] ? '提交审核' : '提交报告审核' }}
  697. </el-button>
  698. <el-button
  699. v-if="selectedItem.reportType === PressureReportType.SUB && selectedItem.taskStatus == PressureCheckerMyTaskStatus.REPORT_INPUT "
  700. type="danger"
  701. @click="handleCompletion"
  702. :disabled="checkerIsLoginUser"
  703. size="small"
  704. >
  705. 报告办结
  706. </el-button>
  707. <el-button type="primary" size="small" plain v-if="isCanEditReport" @click="handleEditRreport" :disabled="checkerIsLoginUser"
  708. >编制报告</el-button>
  709. <el-button v-if="isCanSubmitRecheck" type="primary" @click="handleSelectVerfiyer" :disabled="checkerIsLoginUser" size="small"
  710. >提交校核</el-button>
  711. <el-button
  712. type="primary"
  713. v-if="isCanEditTestRecord"
  714. plain
  715. @click="handleEditSpreadRecord"
  716. :disabled="checkerIsLoginUser"
  717. size="small"
  718. >{{ selectedItem.reportType === PressureReportType['SUGGUESTION'] ? '编制意见书' : '填写记录' }}</el-button>
  719. <!-- 撤销流程:报告审核或审批阶段,有OA流程ID且当前用户是发起人时显示 -->
  720. <el-button
  721. v-if="isCanCancelFlow"
  722. type="warning"
  723. @click="handleCancelFlow"
  724. size="small"
  725. >撤销流程</el-button>
  726. <!-- OA审核:报告审核或审批阶段,有OA流程ID时显示 -->
  727. <el-button
  728. v-if="isCanOAAudit"
  729. type="success"
  730. @click="handleOpenOAAudit"
  731. size="small"
  732. >OA审核</el-button>
  733. <!-- 小程序推送:记录录入状态下,根据项目配置判断是否需要签名 -->
  734. <el-button
  735. v-if="isCanMiniProgramPush"
  736. type="warning"
  737. @click="handleMiniProgramPush"
  738. size="small"
  739. >小程序推送</el-button>
  740. </template>
  741. </div>
  742. </template>
  743. </Teleport>
  744. <AssociationOperationManual
  745. v-if="showAssociationOperationManual"
  746. :selectedItem="selectedItem"
  747. :reportList="reportList"
  748. :equipCode="props.taskOrderItem?.equipCode"
  749. @close="()=>{
  750. showAssociationOperationManual = false
  751. handleRefresh()
  752. }"
  753. />
  754. <!-- 上传整改材料弹窗 -->
  755. <RectificationMaterialsDialog
  756. :visible="ectificationMaterialsVisible"
  757. :report-name="selectedItem?.reportName"
  758. :detail="checkBookDetail"
  759. :submit-api="BoilerTaskOrderApi.inspectionOpinionRectifyApi"
  760. :editable="true"
  761. @close="ectificationMaterialsVisible = false"
  762. @success="handleRefresh"
  763. />
  764. <CustomDialog
  765. ref="customUnitDialogRef"
  766. title="选择审核人"
  767. v-model="approvalUserVisible"
  768. width="650px"
  769. :dialogAttrs="{ zIndex: 10006 }"
  770. >
  771. <el-form ref="formRef" :model="formData" :rules="formRules" label-width="150px">
  772. <el-form-item label="意见通知书审核人" prop="inspectionAuditId">
  773. <el-select v-model="formData.inspectionAuditId" clearable placeholder="请选择">
  774. <el-option
  775. v-for="item in optionList.schemeReviewerList.list"
  776. :key="item.id"
  777. :label="item.nickname"
  778. :value="item.id"
  779. />
  780. </el-select>
  781. </el-form-item>
  782. </el-form>
  783. <template #footer>
  784. <el-button @click="approvalUserVisible = false">取消</el-button>
  785. <el-button type="primary" @click="handleSubmitUser">确定</el-button>
  786. </template>
  787. </CustomDialog>
  788. <!-- 小程序推送签名信息弹窗 -->
  789. <el-dialog
  790. v-model="miniProgramPushDialogVisible"
  791. title="签名信息"
  792. width="500px"
  793. :close-on-click-modal="false"
  794. >
  795. <el-form
  796. ref="miniProgramPushFormRef"
  797. :model="miniProgramPushForm"
  798. :rules="miniProgramPushFormRules"
  799. label-width="100px"
  800. >
  801. <el-form-item label="联系人姓名" prop="recipient">
  802. <el-input
  803. v-model="miniProgramPushForm.recipient"
  804. placeholder="请输入联系人姓名"
  805. clearable
  806. />
  807. </el-form-item>
  808. <el-form-item label="联系人电话" prop="recipientPhone">
  809. <el-input
  810. v-model="miniProgramPushForm.recipientPhone"
  811. placeholder="请输入联系人电话"
  812. clearable
  813. />
  814. </el-form-item>
  815. </el-form>
  816. <template #footer>
  817. <el-button @click="miniProgramPushDialogVisible = false">取消</el-button>
  818. <el-button type="primary" :loading="miniProgramPushLoading" @click="handleMiniProgramPushConfirm">确定推送</el-button>
  819. </template>
  820. </el-dialog>
  821. </template>
  822. <script setup lang="tsx">
  823. import SmartTable from '@/components/SmartTable/SmartTable'
  824. import RectificationUpload from '@/views/pressure2/components/RectificationUpload.vue'
  825. import RectificationMaterialsDialog from '@/views/pressure2/components/RectificationMaterialsDialog.vue'
  826. import { computed, nextTick, reactive, ref, watch } from 'vue'
  827. import { useRouter, useRoute } from 'vue-router'
  828. import { InfoFilled, View, Back, Right, WarningFilled, VideoPlay } from '@element-plus/icons-vue'
  829. import {dayjs, ElLoading, ElMessage, ElMessageBox, FormInstance, FormRules} from 'element-plus'
  830. const CustomDialog = defineAsyncComponent(() => import('@/components/CustomDialog/index.vue'))
  831. import VuePdfEmbed from 'vue-pdf-embed'
  832. import { getReportAllowedApplyModify, getReportCheckPdf } from '@/api/laboratory/functional/report'
  833. import * as UserApi from '@/api/system/user'
  834. import {
  835. PressureCheckerMyTaskStatusMap,
  836. PressureCheckerMyTaskStatus,
  837. PressureReportType,
  838. PressureTaskOrderTaskStatus,
  839. SugguestionApproveConfig,
  840. } from '@/utils/constants'
  841. import { OAApprovalResultMap, OAApprovalResultType } from '@/utils/pressure2/oaConstants'
  842. import type {
  843. ReportItemVO,
  844. BoilerTaskOrderDetailVO,
  845. BoilerTaskOrderOrderItemVO
  846. } from '@/api/pressure2/boilertaskorder'
  847. import InspectionItemProgress from './InspectionItemProgress.vue'
  848. import InlineEditCheckRecord from './InlineEditCheckRecord.vue'
  849. import InlinePdfViewer from './InlinePdfViewer.vue'
  850. import InlineInspectionResultInput from './InlineInspectionResultInput.vue'
  851. import InlineReportEdit from './InlineReportEdit.vue'
  852. import { BoilerTaskOrderApi } from '@/api/pressure2/boilertaskorder'
  853. import { defineAsyncComponent } from 'vue'
  854. import UserSelectForm from '@/components/UserSelectForm/index.vue'
  855. import { reportInfoApi } from '@/api/reportInfoService'
  856. import { buildFileUrl } from '@/utils'
  857. import { useUserStore } from '@/store/modules/user'
  858. const AuditUserDialog = defineAsyncComponent(
  859. () => import('@/views/Functional/components/AuditUserDialog.vue')
  860. )
  861. import { is } from '@/utils/is'
  862. import _ from 'lodash'
  863. import { uploadFile } from '@/api/common/index'
  864. import ReportListUploadModal from './reportListUploadModal.vue'
  865. import AssociationOperationManual from './AssociationOperationManual.vue'
  866. import {DynamicTbInsApi} from "@/api/pressure2/dynamictbins";
  867. import { OrderReportApi } from "@/api/pressure2/orderreport";
  868. import {SpreadViewer} from "@/components/DynamicReport";
  869. import {InitParams} from "@/components/DynamicReport/SpreadInterface";
  870. import { cloneDeep, debounce } from 'lodash-es'
  871. import {PipeTaskOrderApi} from "@/api/pressure2/pipetaskorder";
  872. import {BoilerConnectRecordReportApi} from "@/api/pressure2/boilerconnectrecordreport";
  873. import * as UserGroupApi from '@/api/bpm/userGroup'
  874. interface Props {
  875. selectedItem: ReportItemVO | null
  876. taskInfo: BoilerTaskOrderDetailVO | null
  877. taskOrderItem: BoilerTaskOrderOrderItemVO
  878. isAuditMode?: boolean
  879. reportId?: string | null
  880. reportList?: ReportItemVO[]
  881. taskId: string
  882. teleportBtnRef: Ref<HTMLDivElement | null>
  883. isFullscreen?: boolean
  884. partList?: any[]
  885. }
  886. interface Emits {
  887. (e: 'refresh'): void
  888. (e: 'template-confirm', templateUrl?: string): void
  889. (e: 'modify-checker', item: ReportItemVO): void
  890. (e: 'submit-approval'): void
  891. (e: 'reject'): void
  892. (e: 'cancel'): void
  893. (e: 'submit-ratify'): void
  894. (e: 'update:selected-item', item: ReportItemVO): void
  895. }
  896. const props = defineProps<Props>()
  897. const emit = defineEmits<Emits>()
  898. const route = useRoute()
  899. const userStore = useUserStore()
  900. const schemaFlag = ref<string | null>(null) //等于proofread = 校核 audit=审核
  901. // 获取当前的路由Name
  902. const routeName = computed(() => route.name)
  903. const form = ref<Record<string, any>>({
  904. recheckUser: {}
  905. })
  906. const refreshPdf = ref(true)
  907. // 视图模式状态管理
  908. const viewMode = ref<'normal' | 'inspection-result'>('normal')
  909. // 批量提交的表单
  910. const batchRecheckForm = ref({
  911. reportIds: [],
  912. recheckUser: {}
  913. })
  914. const batchRecheckFormRules = {
  915. reportIds: [{required: true, message: '请选择提交记录', trigger: 'change'}],
  916. recheckUser: [{required: true, message: '请选择校核人', trigger: 'change'}]
  917. }
  918. const batchRecheckFormRef = ref()
  919. // 批量提交校核弹窗的查询和全选逻辑
  920. const batchRecheckSearchKey = ref('')
  921. // 过滤后的可提交校核报告(根据查询关键字,同时匹配部件名)
  922. const filteredBatchRecheckReports = computed(() => {
  923. const keyword = (batchRecheckSearchKey.value || '').trim().toLowerCase()
  924. if (!keyword) return getCanSubmitRecheckReport.value
  925. return getCanSubmitRecheckReport.value.filter((item: any) => {
  926. const name = (item.reportName || '').toLowerCase()
  927. const partName = (getPartNameById(item.itemPartId) || '').toLowerCase()
  928. return name.includes(keyword) || partName.includes(keyword)
  929. })
  930. })
  931. // 当前过滤列表是否全部选中
  932. const batchRecheckCheckAll = computed(() => {
  933. const list = filteredBatchRecheckReports.value
  934. if (!list.length) return false
  935. return list.every((item: any) => batchRecheckForm.value.reportIds.includes(item.id))
  936. })
  937. // 当前过滤列表是否部分选中(用于 indeterminate 状态)
  938. const batchRecheckIndeterminate = computed(() => {
  939. const list = filteredBatchRecheckReports.value
  940. if (!list.length) return false
  941. const checkedCount = list.filter((item: any) => batchRecheckForm.value.reportIds.includes(item.id)).length
  942. return checkedCount > 0 && checkedCount < list.length
  943. })
  944. // 全选/取消全选当前过滤列表
  945. const handleBatchRecheckCheckAll = (val: any) => {
  946. const checked = !!val
  947. const list = filteredBatchRecheckReports.value
  948. const listIds = list.map((item: any) => item.id)
  949. if (checked) {
  950. // 合并去重
  951. const set = new Set([...batchRecheckForm.value.reportIds, ...listIds])
  952. batchRecheckForm.value.reportIds = Array.from(set)
  953. } else {
  954. batchRecheckForm.value.reportIds = batchRecheckForm.value.reportIds.filter((id: any) => !listIds.includes(id))
  955. }
  956. }
  957. // 单个检验项目勾选/取消勾选
  958. const handleBatchRecheckItemCheck = (id: string, val: any) => {
  959. const checked = !!val
  960. const ids = batchRecheckForm.value.reportIds
  961. if (checked) {
  962. if (!ids.includes(id)) ids.push(id)
  963. } else {
  964. batchRecheckForm.value.reportIds = ids.filter((x: string) => x !== id)
  965. }
  966. }
  967. // 根据部件ID获取部件名称
  968. const getPartNameById = (partId: string): string => {
  969. if (!partId || partId === '0') return ''
  970. const part = (props.partList || []).find((item: any) => item.id === partId)
  971. return part?.partName || ''
  972. }
  973. // 获取批量提交校核弹窗中检验项目的显示名称(电站锅炉内检有部件的,项目名前加"部件名-")
  974. const getBatchRecheckReportDisplayName = (item: any): string => {
  975. const partName = getPartNameById(item.itemPartId)
  976. if (partName) {
  977. return `${partName}-${item.reportName || ''}`
  978. }
  979. return item.reportName || ''
  980. }
  981. // const isShowReportPdf = ref([PressureCheckerMyTaskStatus.REPORT_INPUT,PressureCheckerMyTaskStatus.REPORT_AUDIT,PressureCheckerMyTaskStatus.REPORT_APPROVE,PressureCheckerMyTaskStatus.REPORT_END].includes(props.selectedItem.taskStatus)
  982. // && ![400,500,600,700].includes(props.selectedItem.reportType))
  983. const showReportPdfType = ref('record')
  984. const refreshPdfFlag = ref(true)
  985. // const pdfPanelRef = ref<HTMLElement | null>(null)
  986. const hasRecord = ref(true)
  987. const hasReport = ref(true)
  988. // 格式化记录结果状态显示
  989. const formatRecordResult = (record) => {
  990. const resultMap = {
  991. 100: '通过',
  992. 200: '拒绝',
  993. 300: '已阅',
  994. 400: '同意',
  995. 500: '不同意'
  996. }
  997. return resultMap[record.result] || record.result || '-'
  998. }
  999. // 获取记录结果的按钮类型
  1000. const getRecordResultButtonType = (record) => {
  1001. if ([100, 300, 400].includes(record.result)) {
  1002. return 'success'
  1003. } else if ([200, 500].includes(record.result)) {
  1004. return 'danger'
  1005. } else {
  1006. return 'default'
  1007. }
  1008. }
  1009. const reportSpreadRef=ref();
  1010. const spreadRef=ref();
  1011. const initData=ref<InitParams>(
  1012. {
  1013. templateId: '',
  1014. refId: '',
  1015. refName:'',
  1016. insId:'',
  1017. opType: 0, // 0:excel,1: pdf
  1018. manualUrl: '',
  1019. });
  1020. const editSpreadRecordRef=ref();
  1021. const editSpreadReportRef=ref();
  1022. const editData=ref<InitParams>(
  1023. {
  1024. templateId: '',
  1025. refId: '',
  1026. refName:'',
  1027. insId:'',
  1028. opType: 0, // 0:excel,1: pdf
  1029. manualUrl: '',
  1030. });
  1031. const reportInitData=ref<InitParams>(
  1032. {
  1033. templateId: '',
  1034. refId: '',
  1035. refName:'',
  1036. insId:'',
  1037. opType: 1, // 0:excel,1: pdf
  1038. });
  1039. // 选择检验意见审核人
  1040. const formRef = ref<InstanceType<typeof ElForm>>()
  1041. const optionList = reactive<{
  1042. schemeReviewerList: Recordable[]
  1043. }>({
  1044. schemeReviewerList: []
  1045. })
  1046. const formData = ref<Recordable>({})
  1047. const formRules = reactive({
  1048. inspectionAuditId: [{ required: true, message: '请选择审核人', trigger: 'change' }]
  1049. })
  1050. const approvalUserVisible = ref(false)
  1051. const handleSubmitUser = () => {
  1052. formRef.value?.validate(async (valid) => {
  1053. if (valid) {
  1054. const submitResult = await BoilerTaskOrderApi.submitOpinionNoticeApproval({
  1055. id: templateParams.value?.id,
  1056. auditUserIds: [formData.value.inspectionAuditId],
  1057. reportType: PressureReportType['SUGGUESTION']
  1058. })
  1059. if (submitResult) {
  1060. approvalUserVisible.value = false
  1061. // 这里可以做页面刷新
  1062. emit('template-confirm')
  1063. }
  1064. }
  1065. })
  1066. }
  1067. // 获取可以批量提交的报告
  1068. const getCanSubmitRecheckReport = computed(() => {
  1069. if(!props.reportList || !props.reportList.length) return []
  1070. return props.reportList.filter((report: any) => {
  1071. // 主报告、子报告、独审报告才有记录校核
  1072. const reportTypes = [PressureReportType['SUB'], PressureReportType['SINGLE']]
  1073. const status = PressureTaskOrderTaskStatus['RECORD_INPUT'] // 记录录入状态
  1074. // report.reportUrl 已经录入数据
  1075. // if(report.taskStatus !== status || !report.reportUrl) return false
  1076. if(report.taskStatus !== status) return false
  1077. else if(report.reportType === PressureReportType['MAIN'] && userStore.user.id === props.taskOrderItem?.mainCheckerUser?.id) return true
  1078. else
  1079. return reportTypes.includes(report.reportType) && (report.checkUsers?.[0]?.id === userStore.user.id || !report.checkUsers || !report.checkUsers.length)
  1080. // 过滤掉主报告存在未办结检验意见通知书时
  1081. }).filter((report: any) => {
  1082. if(report.reportType !== PressureReportType['MAIN']){
  1083. return true
  1084. }
  1085. const sugguestionReport = props.reportList?.find(x => x.reportType === PressureReportType['SUGGUESTION'])
  1086. // 不存在检验意见通知书返回true
  1087. if(!sugguestionReport) return true
  1088. // 检验意见通知书已办结,返回true
  1089. return sugguestionReport.taskStatus === PressureCheckerMyTaskStatus['REPORT_END'];
  1090. })
  1091. })
  1092. // 当意见通知书未办结时,主报告不能流转到后续流程(提交校核)
  1093. const canSubmitMainReportType = (tipsText: string)=>{
  1094. // 不是主报告,直接返回true
  1095. if(PressureReportType.MAIN !== props?.selectedItem?.reportType) return true
  1096. // 判断检验意见通知书的状态
  1097. const report = props.reportList?.find(x => x.reportType === PressureReportType['SUGGUESTION'])
  1098. console.log('检验意见通知书', report)
  1099. // 不存在检验意见通知书返回true
  1100. if(!report) return true
  1101. // 检验意见通知书已办结,返回true
  1102. if(report.taskStatus === PressureCheckerMyTaskStatus['REPORT_END']) return true
  1103. ElMessageBox.alert(tipsText, '提示', {
  1104. showCancelButton: false,
  1105. confirmButtonText: '确定',
  1106. type: 'warning'
  1107. })
  1108. return false
  1109. }
  1110. // 校核人选择对话框状态
  1111. const showBatchSubmitToRecheckDialog = ref(false)
  1112. const isShowAuditDialog = ref(false)
  1113. const btnLoading = ref(false)
  1114. // 检验意见通知书提交审核
  1115. const sugguestionApproveConfig = ref<any[]>(cloneDeep(SugguestionApproveConfig))
  1116. const isShowSuggestionAuditDialog = ref(false)
  1117. const suggestionFormRef = ref<FormInstance>()
  1118. const suggestionFormData = ref<any>({})
  1119. const suggestionFormRules = reactive({
  1120. sugguestionAuditId: [
  1121. { required: true, message: '请选择审核人', trigger: 'blur' }
  1122. ],
  1123. sugguestionApproveId: [
  1124. { required: true, message: '请选择审批人', trigger: 'blur' }
  1125. ]
  1126. })
  1127. const handleSelectVerfiyer = async () => {
  1128. try {
  1129. await spreadRef.value?.handleSave()
  1130. } catch (error) {
  1131. console.error('保存数据失败:', error)
  1132. ElMessage.error('保存数据失败,请重试')
  1133. return
  1134. }
  1135. const canSubmit = canSubmitMainReportType('该报告存在未办结检验意见通知书,无法提交校核')
  1136. if(!canSubmit) return
  1137. // 获取审核人\校核人配置信息
  1138. let res = await BoilerTaskOrderApi.getDefaultApproval()
  1139. // 如果存在多份待提交校核的报告,执行批量校核弹窗
  1140. if(getCanSubmitRecheckReport.value.length > 1) {
  1141. if(res && res.recheckUser) {
  1142. // 校验默认校核人是否在校核人列表中
  1143. const checkList = await getRecheckUserList({ pageNo: 1, pageSize: 200, nickName: res.recheckUser.nickname })
  1144. const exists = (checkList?.list || []).some((item: any) => item.id === res.recheckUser.id)
  1145. if (exists) {
  1146. batchRecheckForm.value.recheckUser = res.recheckUser
  1147. }
  1148. }
  1149. batchRecheckForm.value.reportIds = getCanSubmitRecheckReport.value.map(x => x?.id)
  1150. batchRecheckSearchKey.value = ''
  1151. showBatchSubmitToRecheckDialog.value = true
  1152. return
  1153. }
  1154. // 只有一份待提交校核的报告,执行以下逻辑
  1155. if(res && res.recheckUser) {
  1156. // 校验默认校核人是否在校核人列表中
  1157. const checkList = await getRecheckUserList({ pageNo: 1, pageSize: 200, nickName: res.recheckUser.nickname })
  1158. const exists = (checkList?.list || []).some((item: any) => item.id === res.recheckUser.id)
  1159. if (exists) {
  1160. form.value.recheckUser = res.recheckUser
  1161. }
  1162. }
  1163. schemaFlag.value = 'proofread'
  1164. isShowAuditDialog.value = true
  1165. }
  1166. // const handleAuditSelectConfirm = async (res: any) => {
  1167. // console.log('handleAuditSelectConfirm', res)
  1168. // if (!props.selectedItem || !res[0]) {
  1169. // return
  1170. // }
  1171. // const saveResult = await BoilerTaskOrderApi.submitTaskReportTemplate({
  1172. // id: props.selectedItem.id,
  1173. // recheckId: res[0]
  1174. // })
  1175. // if (saveResult) {
  1176. // ElMessage.success('提交校核成功!')
  1177. // // 这里可以做页面刷新
  1178. // emit('template-confirm')
  1179. // }
  1180. // }
  1181. // 批量提交
  1182. const handleBatchSubmitToRecheck = async () => {
  1183. try {
  1184. await batchRecheckFormRef.value.validate()
  1185. } catch (err) {
  1186. ElMessage.error('请完善表单数据!')
  1187. return
  1188. }
  1189. if (_.isEmpty(batchRecheckForm.value.recheckUser)) {
  1190. return ElMessage.error('请选择校核人')
  1191. }
  1192. // 待提交的数据
  1193. const params = {
  1194. reportList: getCanSubmitRecheckReport.value.filter((x: any) => batchRecheckForm.value.reportIds.includes(x?.id)).map((x: any) => ({
  1195. id: x.id,
  1196. reportUrl: x.reportUrl,
  1197. dataJson: x?.prepareJson
  1198. })),
  1199. recheckId: batchRecheckForm.value.recheckUser?.id
  1200. }
  1201. const submitResult = await BoilerTaskOrderApi.batchSubmitToRecheck(params)
  1202. if (submitResult) {
  1203. // 保存默认校核人
  1204. BoilerTaskOrderApi.saveDefaultApproval({
  1205. recheckId: batchRecheckForm.value.recheckUser?.id
  1206. })
  1207. ElMessage.success('提交校核成功!')
  1208. showBatchSubmitToRecheckDialog.value = false
  1209. selectNextItem(getCanSubmitRecheckReport.value.filter((x: any) => batchRecheckForm.value.reportIds.includes(x?.id)))
  1210. // 这里可以做页面刷新
  1211. emit('template-confirm')
  1212. }
  1213. }
  1214. const submitting = ref(false)
  1215. const handleAuditSelectConfirm = async () => {
  1216. if (_.isEmpty(form.value.recheckUser)) {
  1217. return ElMessage.error(schemaFlag.value == 'proofread' ? '请选择校核人' : '请选择审核人')
  1218. }
  1219. // 审核
  1220. if (schemaFlag.value === 'audit') {
  1221. submitting.value = true
  1222. const submitResult = await BoilerTaskOrderApi.submitReportAudit({
  1223. id: templateParams.value?.id,
  1224. approveId: form.value?.recheckUser?.id
  1225. })
  1226. submitting.value = false
  1227. if (submitResult) {
  1228. ElMessage.success('提交审核成功!')
  1229. isShowAuditDialog.value = false
  1230. selectNextItem([props.selectedItem])
  1231. // 这里可以做页面刷新
  1232. emit('template-confirm')
  1233. }
  1234. } else if (schemaFlag.value === 'proofread') {
  1235. // 校核
  1236. const saveResult = await BoilerTaskOrderApi.submitTaskReportTemplate({
  1237. id: props?.selectedItem?.id,
  1238. recheckId: form.value?.recheckUser?.id
  1239. })
  1240. if (saveResult) {
  1241. // 保存默认校核人
  1242. BoilerTaskOrderApi.saveDefaultApproval({
  1243. recheckId: form.value?.recheckUser?.id
  1244. })
  1245. ElMessage.success('提交校核成功!')
  1246. isShowAuditDialog.value = false
  1247. selectNextItem([props.selectedItem])
  1248. // 这里可以做页面刷新
  1249. emit('template-confirm')
  1250. }
  1251. }
  1252. }
  1253. /**
  1254. * 项目切换
  1255. */
  1256. const selectNextItem = (items:ReportItemVO[]) => {
  1257. // 找到其他在记录录入或者报告编制状态的第一项目
  1258. const ids = items.map(item => item.id)
  1259. const inputReportList= props.reportList.filter(
  1260. item => {
  1261. return !ids.includes(item.id)
  1262. }
  1263. ).filter(item =>{
  1264. return item.taskStatus === PressureTaskOrderTaskStatus['RECORD_INPUT'] || item.taskStatus === PressureTaskOrderTaskStatus['REPORT_INPUT']
  1265. })
  1266. console.log(inputReportList)
  1267. if (inputReportList.length > 0){
  1268. emit('item-select', inputReportList[0])
  1269. }
  1270. }
  1271. // 审批人选择对话框状态
  1272. const isShowApprovalDialog = ref(false)
  1273. // 监听项目切换,自动重置视图模式
  1274. watch(
  1275. () => props.selectedItem,
  1276. (newItem, oldItem) => {
  1277. if (newItem) {
  1278. showReportPdfType.value = [PressureCheckerMyTaskStatus.REPORT_INPUT,PressureCheckerMyTaskStatus.REPORT_AUDIT,PressureCheckerMyTaskStatus.REPORT_APPROVE,PressureCheckerMyTaskStatus.REPORT_END].includes(props.selectedItem.taskStatus)
  1279. && ![400,500,600,700].includes(props.selectedItem.reportType) ? 'report':'record'
  1280. handleRefreshData()
  1281. props.taskId && getReportAuditInfo()
  1282. hasRecord.value = props.selectedItem.templateId != null
  1283. if (props.selectedItem?.reportTemplateId == null){
  1284. showReportPdfType.value = 'record'
  1285. }
  1286. if (props.selectedItem?.templateId == null){
  1287. showReportPdfType.value = 'report'
  1288. }
  1289. }
  1290. // 当切换到不同的项目时,重置视图模式为normal
  1291. if (newItem?.id !== oldItem?.id) {
  1292. viewMode.value = 'normal'
  1293. }
  1294. },
  1295. { immediate: false }
  1296. )
  1297. const previewUrl = ref('')
  1298. const previewVisible = ref(false)
  1299. const previewType = ref('')
  1300. const handlePreview = (path: string, type: 'video' | 'image') => {
  1301. previewUrl.value = path;
  1302. previewType.value = type;
  1303. previewVisible.value = true;
  1304. }
  1305. /**
  1306. * 如果是"已经审核完的检验意见通知书,则查询相关的检验意见通知信息,显示在最右侧
  1307. */
  1308. const checkBookDetail = ref<Record<string, any>>({})
  1309. const showCheckBook = computed(() => {
  1310. const { reportType, taskStatus } = props.selectedItem || {}
  1311. // console.log(taskStatus, 'taskStatus', reportType)
  1312. // taskStatus: 800
  1313. return reportType === PressureReportType['SUGGUESTION']
  1314. })
  1315. const rectificationStatusMap = reactive({
  1316. 0: '待确认',
  1317. 1: '待整改',
  1318. 2: '已递交',
  1319. 3: '材料有误',
  1320. 4: '整改通过',
  1321. 5: '整改不通过'
  1322. })
  1323. const getReportAuditInfo = async () => {
  1324. if (props.selectedItem?.reportType === PressureReportType['SUGGUESTION']) {
  1325. const res = await BoilerTaskOrderApi.exportCheckBookDetail({
  1326. id: props.selectedItem?.id
  1327. })
  1328. console.log(res, '查询检验意见通知书详细信息')
  1329. if (res) {
  1330. checkBookDetail.value = res
  1331. }
  1332. }
  1333. }
  1334. const returnForm = ref({
  1335. rejectionReason: ''
  1336. })
  1337. const returnFormRules = ref({
  1338. rejectionReason: [{ required: true, message: '请输入回退原因', trigger: 'blur' }]
  1339. })
  1340. const returnFormRef = ref()
  1341. const rejectionReasonDialogVisible = ref(false)
  1342. const tempRejectionReason = ref({ rejectionReason: '' })
  1343. const rejectionReasonDialogFormRef = ref()
  1344. const isCompleteInput = computed(() => {
  1345. return props.selectedItem?.taskStatus >= PressureCheckerMyTaskStatus.RECORD_CHECK
  1346. })
  1347. const isShowRecordOrReportBtn = computed(() => {
  1348. return [PressureReportType.MAIN,PressureReportType.SUB,PressureReportType.SINGLE].includes(props.selectedItem?.reportType)
  1349. })
  1350. // 通过 退回
  1351. const handlePass = async (type) => {
  1352. const params: Record<string, any> = {
  1353. reportId: props.selectedItem?.id,
  1354. approvalType: type
  1355. }
  1356. if (type === 1) {
  1357. if (isExpanded.value) {
  1358. try {
  1359. await returnFormRef.value.validate()
  1360. } catch {
  1361. ElMessage.warning('请填写回退原因')
  1362. return
  1363. }
  1364. params.rejectionReason = returnForm.value.rejectionReason
  1365. executePass(params, type)
  1366. } else {
  1367. tempRejectionReason.value.rejectionReason = returnForm.value.rejectionReason
  1368. rejectionReasonDialogVisible.value = true
  1369. }
  1370. return
  1371. }
  1372. executePass(params, type)
  1373. }
  1374. // 弹出框确认回退
  1375. const handleRejectionReasonDialogConfirm = async () => {
  1376. try {
  1377. await rejectionReasonDialogFormRef.value.validate()
  1378. } catch {
  1379. return
  1380. }
  1381. rejectionReasonDialogVisible.value = false
  1382. // 同步到主表单以便保持数据一致
  1383. returnForm.value.rejectionReason = tempRejectionReason.value.rejectionReason
  1384. const params: Record<string, any> = {
  1385. reportId: props.selectedItem?.id,
  1386. approvalType: 1,
  1387. rejectionReason: tempRejectionReason.value.rejectionReason
  1388. }
  1389. executePass(params, 1)
  1390. }
  1391. // 执行通过/退回操作
  1392. const executePass = (params: Record<string, any>, type: number) => {
  1393. const tipText = type === 1 ? '退回' : type === 2 ? '整改不通过' : '整改通过'
  1394. ElMessageBox.confirm(`确定${tipText}吗?`, '提示', {
  1395. confirmButtonText: '确定',
  1396. cancelButtonText: '取消',
  1397. type: 'warning'
  1398. }).then(() => {
  1399. BoilerTaskOrderApi.inspectionApproval(params).then((res) => {
  1400. props.taskId && getReportAuditInfo()
  1401. emit('refresh')
  1402. }).catch(() => {
  1403. ElMessage.error('操作失败')
  1404. })
  1405. })
  1406. }
  1407. // 上传整改材料
  1408. const ectificationMaterialsVisible = ref(false)
  1409. const handleRectificationMaterials = () => {
  1410. ectificationMaterialsVisible.value = true
  1411. checkBookDetail.value.id = props.selectedItem?.id
  1412. }
  1413. // 获取检验员名称
  1414. const getCheckersName = (): string => {
  1415. if (!props.selectedItem?.checkUsers || props.selectedItem.checkUsers.length === 0) {
  1416. return '未分配'
  1417. }
  1418. return props.selectedItem.checkUsers.map((user) => user.nickname).join('、')
  1419. }
  1420. // 切换主检人
  1421. const handleUserSelect = async (id, userList) => {
  1422. const result = await BoilerTaskOrderApi.updateReportMainChecker({
  1423. id: props.taskOrderItem?.id,
  1424. mainChecker: userList[0]?.id
  1425. })
  1426. if (result) {
  1427. ElMessage.success('修改主检人成功!')
  1428. emit('refresh')
  1429. }
  1430. }
  1431. const userSelectFormRef = ref()
  1432. const handleModifyMainChecker = () => {
  1433. userSelectFormRef.value.open(props.taskOrderItem?.mainCheckerUser?.id)
  1434. }
  1435. // 判断当前检验员或主检人是否为登录用户,如果不是,则功能按钮disabled
  1436. const checkerIsLoginUser = computed(() => {
  1437. const checkerUserIds = props.selectedItem?.checkUsers.map(checker => checker?.id)
  1438. return !(checkerUserIds?.includes(userStore?.user?.id) || props.taskOrderItem?.mainCheckerUser?.id === userStore?.user?.id)
  1439. })
  1440. // 判断当前项目是否已经到报告办结阶段
  1441. const getReportStatusEnd = computed(
  1442. () =>{
  1443. return props.selectedItem && props.selectedItem.taskStatus >= PressureCheckerMyTaskStatus['REPORT_CONFIRMATION']
  1444. }
  1445. )
  1446. // 判断“提交校核”按钮是否显示
  1447. const isCanSubmitRecheck = computed(() => {
  1448. if (props.selectedItem?.reportType === PressureReportType['SUGGUESTION'])
  1449. return props.selectedItem?.isRecheck || false
  1450. else if (props.selectedItem?.reportType === PressureReportType['MAINQUESTION']) return false
  1451. else
  1452. return (
  1453. props.selectedItem &&
  1454. props.selectedItem.taskStatus === PressureCheckerMyTaskStatus['RECORD_INPUT'] &&
  1455. !isSingleReportWithoutInstruction.value
  1456. // && props.selectedItem.reportUrl
  1457. )
  1458. })
  1459. // 判断“提交审核&提交审批”按钮是否显示
  1460. const isCanSubmitToAudit = computed(() => {
  1461. const { selectedItem } = props
  1462. if (selectedItem && selectedItem?.reportType === PressureReportType['SUBCONTRACT']){
  1463. return selectedItem.taskStatus === PressureCheckerMyTaskStatus['CONFIRMED']
  1464. }
  1465. if (props.selectedItem && props.selectedItem?.reportType === PressureReportType['SUGGUESTION'])
  1466. return (
  1467. props.selectedItem?.isApproval &&
  1468. props.selectedItem.taskStatus === PressureCheckerMyTaskStatus['RECORD_INPUT']
  1469. )
  1470. else if (
  1471. props.selectedItem &&
  1472. props.selectedItem?.reportType === PressureReportType['MAINQUESTION']
  1473. )
  1474. return (
  1475. props.selectedItem.taskStatus === PressureCheckerMyTaskStatus['RECORD_INPUT'] &&
  1476. props.selectedItem?.reportUrl
  1477. )
  1478. else
  1479. return (
  1480. props.selectedItem &&
  1481. props.selectedItem.taskStatus === PressureCheckerMyTaskStatus['REPORT_INPUT']
  1482. )
  1483. })
  1484. // 独审报告未关联操作指导书时不可编辑
  1485. const isSingleReportWithoutInstruction = computed(
  1486. () =>
  1487. props.selectedItem?.reportType === PressureReportType.SINGLE &&
  1488. !props.selectedItem?.instructionId
  1489. )
  1490. // 判断“填写记录”按钮是否显示
  1491. const isCanEditTestRecord = computed(
  1492. () =>
  1493. props.selectedItem &&
  1494. (props.selectedItem.taskStatus === PressureCheckerMyTaskStatus['RECORD_INPUT'] ||
  1495. props.selectedItem.taskStatus === PressureCheckerMyTaskStatus['CONFIRMED']) && props.selectedItem.reportType !== PressureReportType['SUBCONTRACT'] &&
  1496. !isSingleReportWithoutInstruction.value
  1497. )
  1498. // 判断“编制报告”按钮是否显示
  1499. const isCanEditReport = computed(
  1500. () =>
  1501. props.selectedItem &&
  1502. props.selectedItem.taskStatus === PressureCheckerMyTaskStatus['REPORT_INPUT'] &&
  1503. !isSingleReportWithoutInstruction.value
  1504. )
  1505. // 判断"撤销流程"按钮是否显示:报告审核或审批阶段,且有OA流程ID,且当前用户是发起人
  1506. const isCanCancelFlow = computed(() => {
  1507. if (!props.selectedItem) return false
  1508. const isAuditOrApprove = [
  1509. PressureCheckerMyTaskStatus['REPORT_AUDIT'],
  1510. PressureCheckerMyTaskStatus['REPORT_APPROVE']
  1511. ].includes(props.selectedItem.taskStatus)
  1512. if (!isAuditOrApprove) return false
  1513. if (!props.selectedItem.summaryId) return false
  1514. return !checkerIsLoginUser.value
  1515. })
  1516. // 撤销流程
  1517. const handleCancelFlow = async () => {
  1518. if (!props.selectedItem?.summaryId) {
  1519. ElMessage.warning('未找到OA流程ID,无法撤销')
  1520. return
  1521. }
  1522. ElMessageBox.confirm('确定要撤销该流程吗?撤销后报告将回到报告编制阶段,您可以重新发起。', '撤销流程', {
  1523. confirmButtonText: '确定',
  1524. cancelButtonText: '取消',
  1525. type: 'warning'
  1526. }).then(async () => {
  1527. try {
  1528. const result = await BoilerTaskOrderApi.cancelOAFlow(props.selectedItem.summaryId)
  1529. if (result) {
  1530. ElMessage.success('流程已撤销')
  1531. emit('refresh')
  1532. }else {
  1533. ElMessage.error('流程已撤销')
  1534. emit('refresh')
  1535. }
  1536. } catch (error) {
  1537. console.error('撤销流程失败:', error)
  1538. ElMessage.error('撤销流程失败,请稍后重试')
  1539. }
  1540. }).catch(() => {})
  1541. }
  1542. const oaSummaryList = ref([])
  1543. // 判断"OA审核"按钮是否显示:报告审核或审批阶段,且有OA流程ID,且当前用户是对应阶段的审核人/审批人
  1544. const isCanOAAudit = computed(() => {
  1545. if (!props.selectedItem) return false
  1546. // const { taskStatus, summaryId, approvalId, ratifyId } = props.selectedItem
  1547. const {summaryId,taskStatus} = props.selectedItem
  1548. // const currentUserId = userStore?.user?.id
  1549. // 审核阶段:只有审核人才能点击
  1550. // 审批阶段:只有审批人才能点击
  1551. if (taskStatus === PressureCheckerMyTaskStatus['REPORT_AUDIT'] || taskStatus === PressureCheckerMyTaskStatus['REPORT_APPROVE']) {
  1552. // 检查是否在OA待办事项中
  1553. const isOaSummary = oaSummaryList.value.some(item => item == summaryId)
  1554. if (!isOaSummary) return false
  1555. return true
  1556. }
  1557. return false
  1558. })
  1559. // OA审核:弹出独立窗口
  1560. const oaAuditTimer = ref<ReturnType<typeof setInterval> | null>(null)
  1561. onMounted(() => {
  1562. setTimeout(() => {
  1563. if (route.query?.type == 'reportRatify' || route.query?.type == 'reportAudit') {
  1564. if (isCanOAAudit.value) {
  1565. handleOpenOAAudit()
  1566. }
  1567. }
  1568. }, 500)
  1569. })
  1570. const handleOpenOAAudit = async () => {
  1571. // 清除之前的定时器
  1572. if (oaAuditTimer.value) {
  1573. clearInterval(oaAuditTimer.value)
  1574. oaAuditTimer.value = null
  1575. }
  1576. // 先打开空白弹窗
  1577. // 先打开全屏空白弹窗
  1578. const win = window.open('', 'OAAudit', 'width=' + screen.width + ',height=' + screen.height + ',left=0,top=0,menubar=no,toolbar=no,location=no,status=no,scrollbars=yes,resizable=yes')
  1579. try {
  1580. const link = await BoilerTaskOrderApi.getAffairLink(props.selectedItem.summaryId)
  1581. if (link && win) {
  1582. win.location.href = link
  1583. // 轮询监听窗口关闭,关闭后触发报告状态更新
  1584. oaAuditTimer.value = setInterval(() => {
  1585. if (win.closed) {
  1586. clearInterval(oaAuditTimer.value!)
  1587. oaAuditTimer.value = null
  1588. BoilerTaskOrderApi.updateReportBySummaryId(props.selectedItem.summaryId).finally(() => emit('refresh'))
  1589. BoilerTaskOrderApi.getAffairSummaryList().then((res) => {
  1590. oaSummaryList.value = res || []
  1591. })
  1592. }
  1593. }, 100)
  1594. } else if (!win) {
  1595. ElMessage.warning('浏览器拦截了弹窗,请允许弹窗后重试')
  1596. } else {
  1597. win.close()
  1598. ElMessage.warning('未找到对应的OA审批链接')
  1599. }
  1600. } catch (error) {
  1601. console.error('获取OA审批链接失败:', error)
  1602. win?.close()
  1603. ElMessage.error('获取OA审批链接失败,请稍后重试')
  1604. }
  1605. }
  1606. // 小程序推送相关逻辑
  1607. const projectConfig = ref<any>({})
  1608. const miniProgramPushDialogVisible = ref(false)
  1609. const miniProgramPushForm = ref({
  1610. recipient: '',
  1611. recipientPhone: ''
  1612. })
  1613. const miniProgramPushFormRules = {
  1614. recipient: [{ required: true, message: '请输入联系人姓名', trigger: 'blur' }],
  1615. recipientPhone: [{ required: true, message: '请输入联系人电话', trigger: 'blur' }]
  1616. }
  1617. const miniProgramPushFormRef = ref()
  1618. const miniProgramPushLoading = ref(false)
  1619. // 判断是否可以小程序推送:记录录入状态下且项目配置需要签名
  1620. const isCanMiniProgramPush = computed(() => {
  1621. if (!props.selectedItem) return false
  1622. // 只有记录录入状态下才能推送
  1623. if (props.selectedItem.taskStatus !== PressureCheckerMyTaskStatus['RECORD_INPUT']) return false
  1624. // 需要有模板ID才能获取项目配置
  1625. if (!props.selectedItem.templateId) return false
  1626. // 项目配置需要签名才显示
  1627. return projectConfig.value.isSign === '1'
  1628. })
  1629. // 获取项目配置
  1630. const fetchProjectConfig = async () => {
  1631. if (!props.selectedItem?.templateId) return
  1632. try {
  1633. const type = showReportPdfType.value === 'report' ? 'report' : 'record'
  1634. const res = await BoilerConnectRecordReportApi.getByTemplateId({
  1635. templateId: props.selectedItem.templateId,
  1636. type: type
  1637. })
  1638. if (res && res.length > 0) {
  1639. projectConfig.value = res[0]
  1640. } else {
  1641. projectConfig.value = {}
  1642. }
  1643. } catch (error) {
  1644. console.error('获取项目配置失败:', error)
  1645. }
  1646. }
  1647. // 处理小程序推送
  1648. const handleMiniProgramPush = async () => {
  1649. await fetchProjectConfig()
  1650. // 判断是否需要签名
  1651. if (projectConfig.value.isSign === '1') {
  1652. // 需要签名,打开签名信息弹窗
  1653. miniProgramPushForm.value = {
  1654. recipient: props.taskOrderItem?.unitContact || '',
  1655. recipientPhone: props.taskOrderItem?.unitPhone || ''
  1656. }
  1657. miniProgramPushDialogVisible.value = true
  1658. } else {
  1659. // 不需要签名,直接推送
  1660. // ElMessageBox.confirm('确定要进行小程序推送吗?', '提示', {
  1661. // confirmButtonText: '确定',
  1662. // cancelButtonText: '取消',
  1663. // type: 'warning'
  1664. // }).then(() => {
  1665. // executeMiniProgramPush()
  1666. // }).catch(() => {})
  1667. }
  1668. }
  1669. // 执行小程序推送 - 更新报告的接收人信息
  1670. const executeMiniProgramPush = async (signInfo?: any) => {
  1671. miniProgramPushLoading.value = true
  1672. try {
  1673. const params: Record<string, any> = {
  1674. id: props.selectedItem?.id
  1675. }
  1676. if (signInfo) {
  1677. params.recipient = signInfo.recipient
  1678. params.recipientPhone = signInfo.recipientPhone
  1679. }
  1680. await BoilerTaskOrderApi.miniProgramPush(params)
  1681. ElMessage.success('推送信息保存成功')
  1682. miniProgramPushDialogVisible.value = false
  1683. emit('refresh')
  1684. } catch (error) {
  1685. console.error('推送信息保存失败:', error)
  1686. ElMessage.error('推送信息保存失败,请稍后重试')
  1687. } finally {
  1688. miniProgramPushLoading.value = false
  1689. }
  1690. }
  1691. // 签名信息弹窗确认
  1692. const handleMiniProgramPushConfirm = async () => {
  1693. try {
  1694. await miniProgramPushFormRef.value?.validate()
  1695. executeMiniProgramPush(miniProgramPushForm.value)
  1696. } catch (error) {
  1697. // 表单验证失败
  1698. }
  1699. }
  1700. // 判断"填写结果"按钮是否显示
  1701. const isCanEditRecordResult = computed(() => {
  1702. // 非我的任务详情页面,不显示
  1703. if ('BoilerCheckerTaskDetail' !== routeName.value) return false
  1704. if (!props.selectedItem?.isAutoAmount || props.selectedItem?.isAutoAmount === '0') return false
  1705. if (
  1706. (props.selectedItem?.reportType === 100 || props.selectedItem?.reportType === 300) &&
  1707. props.selectedItem.taskStatus !== PressureCheckerMyTaskStatus['REPORT_END']
  1708. ) {
  1709. // 独审报告&主报告--“填写结果”:报告办结之前都可以填写
  1710. return true
  1711. }
  1712. // 查找主报告
  1713. const mainReport = props.reportList?.find((item) => item.reportType === 100) || {
  1714. taskStatus: null
  1715. }
  1716. if (
  1717. props.selectedItem?.reportType !== 100 &&
  1718. props.selectedItem?.reportType !== 300 &&
  1719. mainReport?.taskStatus !== PressureCheckerMyTaskStatus['REPORT_END']
  1720. ) {
  1721. // 非主报告 & 非独审报告 -- “填写结果”:主报告办结之前都可以填写
  1722. return true
  1723. }
  1724. return false
  1725. })
  1726. // 1、重大问题线索告知表 2、作业指导书 3、检验方案 只显示pdf
  1727. const onlyShowPdf = computed(() => {
  1728. const reportTypes = [
  1729. PressureReportType.MAINQUESTION,
  1730. PressureReportType.INSPECTIONPLAN,
  1731. PressureReportType.WORKINSTRUCTION
  1732. ]
  1733. return !props.selectedItem?.reportType
  1734. ? false
  1735. : reportTypes.includes(props.selectedItem?.reportType)
  1736. })
  1737. // 模板参数
  1738. const templateParams = computed(() => {
  1739. if (!props.selectedItem) return {}
  1740. return {
  1741. ...props.selectedItem,
  1742. equipCode: props.taskOrderItem?.equipCode || '',
  1743. equipId: props.taskOrderItem?.id || ''
  1744. }
  1745. })
  1746. // 判断是否为独审报告且不需要审批
  1747. const isApprovalWithoutRatify = computed(() => {
  1748. if (!props.selectedItem) return false
  1749. const reportType = (props.selectedItem as any).reportType
  1750. const isApproval = (props.selectedItem as any).isApproval
  1751. const isRatify = (props.selectedItem as any).isRatify
  1752. return (
  1753. reportType === PressureReportType.SINGLE &&
  1754. isApproval === true &&
  1755. isRatify === false &&
  1756. props.reportId === props.selectedItem.id
  1757. )
  1758. })
  1759. /**** 葡萄城:记录录入的显示和隐藏 ****/
  1760. const showInlineEditCheckRecord = ref(false)
  1761. const templateId = ref('')
  1762. const handleEditSpreadRecord = () => {
  1763. // console.log('templateParams', templateParams.value)
  1764. // DynamicTbInsApi.getOrCreatePreviewData('dc8fbb6078b2a2f0eadc0f6aedbeafa0').then(res => {
  1765. // showInlineEditCheckRecord.value = true
  1766. // templateId.value = res.id
  1767. // })
  1768. editPreview('record')
  1769. showInlineEditCheckRecord.value = true
  1770. }
  1771. const handleTemplateConfirm = (templateUrl: string) => {
  1772. console.log('templateUrl', templateUrl)
  1773. emit('template-confirm', templateUrl)
  1774. }
  1775. const handleRefresh = () => {
  1776. emit('refresh')
  1777. }
  1778. /**** 葡萄城:记录录入的显示和隐藏 end****/
  1779. /**** 葡萄城:检验结果录入的显示和隐藏 ****/
  1780. const showInlineInspectionResultInput = ref(false)
  1781. const inspectionResultTemplateParams = ref({})
  1782. // 检验结果录入模板参数
  1783. const handleInputCheckConclusion = () => {
  1784. showInlineInspectionResultInput.value = true
  1785. if (!props.selectedItem) return {}
  1786. inspectionResultTemplateParams.value = {
  1787. ...props.selectedItem,
  1788. equipCode: props.taskOrderItem?.equipCode || ''
  1789. }
  1790. }
  1791. const handleInspectionResultConfirm = (resultUrl: string) => {
  1792. emit('template-confirm', resultUrl)
  1793. viewMode.value = 'normal'
  1794. }
  1795. const handleReturnToNormalMode = () => {
  1796. viewMode.value = 'normal'
  1797. }
  1798. /**** 葡萄城:检验结果录入的显示和隐藏 end ****/
  1799. /**** 葡萄城:报告编制的显示和隐藏 ****/
  1800. const showInlineReportEdit = ref(false)
  1801. const handleEditRreport = () => {
  1802. if (showReportPdfType.value !== 'report' && showReportPdfType.value !== 'result'){
  1803. ElMessage.warning('请切换报告后再进行报告编制!')
  1804. return
  1805. }
  1806. editPreview('report')
  1807. showInlineReportEdit.value = true
  1808. }
  1809. // 获取当前报告的配置信息
  1810. const currentReportConfig = computed(() => {
  1811. if (
  1812. !props.selectedItem ||
  1813. props.selectedItem.reportType !== PressureReportType.SINGLE ||
  1814. !props.reportList
  1815. ) {
  1816. return null
  1817. }
  1818. // 从reportList中找到当前报告的配置
  1819. const currentReport = props.reportList.find((report) => report.id === templateParams.value?.id)
  1820. return currentReport
  1821. ? {
  1822. isApproval: (currentReport as any).isApproval,
  1823. isRatify: (currentReport as any).isRatify
  1824. }
  1825. : null
  1826. })
  1827. // 选择报告审核人弹窗的显示和隐藏
  1828. const isShowReportAuditDialog = ref(false)
  1829. // 弹窗打开时预加载审核人列表和默认值
  1830. watch(isShowReportAuditDialog, async (val) => {
  1831. if (val) {
  1832. reportAuditUserCode.value = ''
  1833. reportAuditUserLoading.value = true
  1834. // 加载审核人列表
  1835. const userList = await getReviewUserList({ pageNo: 1, pageSize: 200, name: '' }).finally(() => {
  1836. reportAuditUserLoading.value = false
  1837. })
  1838. reportAuditUserOptions.value = userList || []
  1839. // 加载默认审核人(工号),校验是否在列表中
  1840. try {
  1841. const defaultApproval = await BoilerTaskOrderApi.getDefaultApproval()
  1842. if (defaultApproval?.approveEmployeeNo) {
  1843. const exists = (userList || []).some((item: any) => item.code === defaultApproval.approveEmployeeNo)
  1844. if (exists) {
  1845. reportAuditUserCode.value = defaultApproval.approveEmployeeNo
  1846. }
  1847. }
  1848. } catch (e) {
  1849. // ignore
  1850. }
  1851. }
  1852. })
  1853. // 提交报告审核
  1854. const handleSubmitAudit = async () => {
  1855. try {
  1856. await spreadRef.value?.handleSave()
  1857. } catch (error) {
  1858. console.error('保存数据失败:', error)
  1859. ElMessage.error('保存数据失败,请重试')
  1860. return
  1861. }
  1862. // 主报告需检查签名状态(金额变动后签名会被置空)
  1863. if (props?.selectedItem?.reportType === PressureReportType.MAIN) {
  1864. try {
  1865. const hasValidSign = await OrderReportApi.hasValidSignByOrderId(props.taskInfo?.id || props.taskId)
  1866. if (!hasValidSign) {
  1867. await ElMessageBox.alert(
  1868. '任务单金额已发生变动,客户签名已失效,请重新推送客户签名后再提交审核',
  1869. '无法提交审核'
  1870. )
  1871. return
  1872. }
  1873. } catch (error) {
  1874. console.error('检查签名状态失败:', error)
  1875. ElMessage.error('检查签名状态失败,请重试')
  1876. return
  1877. }
  1878. }
  1879. // if([PressureReportType.SUGGUESTION, PressureReportType.MAINQUESTION].includes(props?.selectedItem?.reportType) && !props?.selectedItem?.reportUrl) {
  1880. // // 【重大问题线索告知表,检验意见通知书】必须先填写记录,才能提交审核
  1881. // ElMessage.warning('请先“填写记录”!再提交审核!')
  1882. // return
  1883. // }
  1884. if(PressureReportType.SINGLE === props?.selectedItem?.reportType && !props?.selectedItem?.prepareUrl) {
  1885. // 独审报告:用户可以不走报告编制环节直接提交审核,需要调用handleUploadAPIReportPreviewBlob将reportPreview接口的文件流拿来提交一遍
  1886. // return handleUploadAPIReportPreviewBlob(handleSubmitAudit)
  1887. isShowReportAuditDialog.value = true
  1888. return
  1889. }
  1890. // 检查检验意见通知书是否“报告完结”
  1891. const canSubmit = canSubmitMainReportType('该报告存在未办结检验意见通知书,无法提交审核、审批')
  1892. if(!canSubmit) return
  1893. // 检查其他报告状态
  1894. const checkResult = checkOtherReportsFinished()
  1895. if (!checkResult.canSubmit) {
  1896. try {
  1897. await ElMessageBox.alert(
  1898. checkResult.message || '请先办结其他报告再提交主报告审核',
  1899. '无法提交审核'
  1900. )
  1901. } catch (error) {
  1902. // 用户关闭对话框,不需要处理
  1903. }
  1904. return
  1905. }
  1906. // if(PressureReportType.MAIN === props?.selectedItem?.reportType && !props?.selectedItem?.prepareUrl) {
  1907. // // 主报告:用户可以不走报告编制环节直接提交审核,需要调用handleUploadAPIReportPreviewBlob将reportPreview接口的文件流拿来提交一遍
  1908. // return handleUploadAPIReportPreviewBlob(handleSubmitAudit)
  1909. // }
  1910. // 重大问题线索通知 不需要选择审核人
  1911. if ([PressureReportType['MAINQUESTION']].includes(templateParams.value?.reportType)) {
  1912. ElMessageBox.confirm(`确定提交【${templateParams.value?.reportName}】`, '提示', {
  1913. confirmButtonText: '确定',
  1914. cancelButtonText: '取消'
  1915. })
  1916. .then(async () => {
  1917. const submitResult = await BoilerTaskOrderApi.submitOpinionNoticeApproval({
  1918. id: templateParams.value?.id
  1919. })
  1920. if (submitResult) {
  1921. // 这里可以做页面刷新
  1922. emit('template-confirm')
  1923. }
  1924. })
  1925. .catch(() => {
  1926. console.log('用户取消提交审核')
  1927. })
  1928. } else if ([PressureReportType['SUGGUESTION']].includes(templateParams.value?.reportType)) {
  1929. optionList.schemeReviewerList = await BoilerTaskOrderApi.getAuditList({
  1930. roleCode: 'Boiler Director'
  1931. })
  1932. approvalUserVisible.value = true
  1933. } else {
  1934. // 确定审核人:使用当前用户,否则使用默认审核人
  1935. // const approveId = userStore.user.id
  1936. // const approveId = 'b5369aeb73954430eef53a9c8b7586ee'
  1937. //
  1938. // ElMessageBox.confirm('确定提交审核吗?', '提示', {
  1939. // confirmButtonText: '确定',
  1940. // cancelButtonText: '取消',
  1941. // type: 'warning'
  1942. // }).then(async () => {
  1943. // const loadingInstance = ElLoading.service({
  1944. // fullscreen: true,
  1945. // text: '正在提交审核...'
  1946. // })
  1947. // try {
  1948. // const submitResult = await BoilerTaskOrderApi.submitReportAudit({
  1949. // id: templateParams.value?.id,
  1950. // approveId
  1951. // })
  1952. // if (submitResult) {
  1953. // ElMessage.success('提交审核成功!')
  1954. // selectNextItem([props.selectedItem])
  1955. // emit('template-confirm')
  1956. // }
  1957. // } finally {
  1958. // loadingInstance.close()
  1959. // }
  1960. // }).catch(() => {
  1961. // console.log('用户取消提交审核')
  1962. // })
  1963. isShowReportAuditDialog.value = true
  1964. }
  1965. }
  1966. const handleCompletion = async () => {
  1967. await BoilerTaskOrderApi.handleCompletion(props.selectedItem.id)
  1968. handleRefresh()
  1969. ElMessage.success('报告已办结')
  1970. }
  1971. const handleOpenSuggestionAuditDialog = () => {
  1972. isShowSuggestionAuditDialog.value = true
  1973. btnLoading.value = true
  1974. UserApi.getApprovalDetail({})
  1975. .then(async (res) => {
  1976. if (res) {
  1977. const opinionNotificationJsonData = res.opinionNotificationJson ? JSON.parse(res.opinionNotificationJson) : {}
  1978. suggestionFormData.value = {
  1979. ...res,
  1980. ...opinionNotificationJsonData,
  1981. }
  1982. const suggestionFilds = sugguestionApproveConfig.value.map((item) => item.filed)
  1983. const suggestionPromise = suggestionFilds.map((category) =>
  1984. UserGroupApi.getUserGroupUserList({ category: category, status: 0 })
  1985. )
  1986. const suggestionResult = await Promise.all(suggestionPromise)
  1987. sugguestionApproveConfig.value.forEach((item, index) => {
  1988. const auditOptions = suggestionResult[index] || []
  1989. item.auditOptions = auditOptions
  1990. const auditId = suggestionFormData.value[item.auditIdProp]
  1991. const auditUser = auditOptions.find((v) => v.id === auditId) || {}
  1992. suggestionFormData.value[item.auditIdPropUser] = { ...auditUser, nickname: `${auditUser.nickname || ''}${auditUser.employeeNo ? `(${auditUser.employeeNo})` : ''}` }
  1993. })
  1994. }
  1995. }).finally(() => {
  1996. btnLoading.value = false
  1997. })
  1998. }
  1999. const handleSubmitSuggestionAudit = () => {
  2000. suggestionFormRef.value?.validate().then(async (valid) => {
  2001. if (valid) {
  2002. btnLoading.value = true
  2003. const params = {
  2004. id: templateParams.value?.id,
  2005. auditUserIds: [suggestionFormData.value?.sugguestionAuditId],
  2006. approveUserIds: [suggestionFormData.value?.sugguestionApproveId]
  2007. }
  2008. BoilerTaskOrderApi.submitOpinionNoticeApproval(params).then((submitResult) => {
  2009. if (submitResult) {
  2010. emit('template-confirm')
  2011. }
  2012. }).catch(() => {
  2013. btnLoading.value = false
  2014. ElMessage.error('提交失败')
  2015. }).finally(() => {
  2016. btnLoading.value = false
  2017. isShowSuggestionAuditDialog.value = false
  2018. })
  2019. }
  2020. })
  2021. }
  2022. const getUserLists = async (userList, params) => {
  2023. const nickname = params.nickname
  2024. const list = nickname ? userList.filter((item) => item.nickname.includes(nickname)) : userList
  2025. const optList = list.map((item) => ({
  2026. ...item,
  2027. nickname: `${item.nickname}${item.employeeNo ? `(${item.employeeNo})` : ''}`,
  2028. }))
  2029. return Promise.resolve({ list: optList, total: list.length })
  2030. }
  2031. const handleChangeHandler = (val, prop) => {
  2032. const { id } = val
  2033. suggestionFormData.value[prop] = id
  2034. }
  2035. const getRecheckUserList = async (params) => {
  2036. params.orderId = props.taskInfo.id
  2037. params.orderItemId = props.taskOrderItem?.id
  2038. return await BoilerTaskOrderApi.getRecheckUserList(params)
  2039. }
  2040. const getReviewUserList = async (params) => {
  2041. params.reportId = props.selectedItem.id
  2042. return await BoilerTaskOrderApi.getReviewUserList(params)
  2043. }
  2044. // 上传主报告文件流
  2045. const handleUploadAPIReportPreviewBlob = async (submitApprovalFn) => {
  2046. if(!props.selectedItem || props.selectedItem.taskStatus !== PressureTaskOrderTaskStatus.REPORT_INPUT) return
  2047. // 获取文件流
  2048. const blob = await BoilerTaskOrderApi.getReportPreview({
  2049. reportId: props.selectedItem?.id,
  2050. type: 300, // 报告模板
  2051. fileType: 100 // xlsx
  2052. })
  2053. if(blob) {
  2054. // 上传文件流
  2055. const formData = new FormData()
  2056. formData.append('file', blob)
  2057. const response = await uploadFile(formData)
  2058. // 保存报告编制的url
  2059. const saveResult = await BoilerTaskOrderApi.saveReportPrepare({
  2060. id: props.selectedItem.id,
  2061. prepareUrl: response,
  2062. })
  2063. if(saveResult) {
  2064. await emit('update:selected-item', {...props.selectedItem, prepareUrl: response})
  2065. // 提交审核
  2066. await submitApprovalFn()
  2067. }
  2068. }
  2069. }
  2070. const handleChangeEntrustUnit = (unit: Record<string, any>) => {
  2071. form.value.recheckUser = unit
  2072. }
  2073. // 检查其他报告是否已办结
  2074. const checkOtherReportsFinished = (): { canSubmit: boolean; message?: string } => {
  2075. if (!props.selectedItem) return { canSubmit: false }
  2076. // 如果不是主报告,直接允许提交
  2077. if (props.selectedItem.reportType !== PressureReportType.MAIN) {
  2078. return { canSubmit: true }
  2079. }
  2080. // 主报告需要检查其他报告状态
  2081. const unfinishedReports = !props.reportList
  2082. ? []
  2083. : props.reportList.filter((report) => {
  2084. // 排除当前报告
  2085. if (report.id === templateParams.value?.id) {
  2086. return false
  2087. }
  2088. // 检查子报告和独审报告是否已办结
  2089. const isSubReport = report.reportType === PressureReportType.SUB
  2090. const isSingleReport = report.reportType === PressureReportType.SINGLE
  2091. const isSUGGUESTIONReport = report.reportType === PressureReportType.SUGGUESTION
  2092. if (isSubReport || isSingleReport || isSUGGUESTIONReport) {
  2093. return report.taskStatus !== PressureCheckerMyTaskStatus.REPORT_END
  2094. }
  2095. return false
  2096. })
  2097. if (unfinishedReports.length > 0) {
  2098. const unfinishedNames = unfinishedReports.map((report) => report.reportName).join('、')
  2099. return {
  2100. canSubmit: false,
  2101. message: `请先办结其他报告再提交主报告审核。未办结的报告:${unfinishedNames}`
  2102. }
  2103. }
  2104. // 检查是否确认费用
  2105. // const reportsWithoutConfirmFee: any[] = []
  2106. // 检查当前报告中是否存在应确认费用,但未确认的报错
  2107. const reportsWithoutConfirmFee: any[] = !props.reportList
  2108. ? []
  2109. : props.reportList.filter(
  2110. (report: any) => report.isAutoAmount === '1' && report.feeConfirm === false
  2111. )
  2112. // if (currentReport) {
  2113. // reportsWithoutConfirmFee.push(currentReport)
  2114. // }
  2115. // 检查子报告是否都已录入结果
  2116. // const subReports = !props.reportList ? [] : props.reportList.filter(report => {
  2117. // return report.id !== templateParams.value?.id &&
  2118. // report.reportType === PressureReportType.SUB
  2119. // })
  2120. // subReports.forEach(report => {
  2121. // if (!report.formulaJson) {
  2122. // reportsWithoutConfirmFee.push(report)
  2123. // }
  2124. // })
  2125. if (reportsWithoutConfirmFee.length > 0) {
  2126. const reportNames = reportsWithoutConfirmFee.map((report) => report.reportName).join('、')
  2127. return {
  2128. canSubmit: false,
  2129. message: `请先确认费用再提交主报告审核。未确认费用的报告:${reportNames}`
  2130. }
  2131. }
  2132. return { canSubmit: true }
  2133. }
  2134. // 报告审核人选择对话框 - 远程搜索选项
  2135. const reportAuditUserCode = ref<string>('')
  2136. const reportAuditUserOptions = ref<Recordable[]>([])
  2137. const reportAuditUserLoading = ref(false)
  2138. const reportAuditSubmitting = ref(false)
  2139. // 报告审核人选择确认
  2140. const handleReportAuditSelectConfirm = async () => {
  2141. if (!reportAuditUserCode.value) {
  2142. ElMessage.warning('请选择审核人')
  2143. return
  2144. }
  2145. reportAuditSubmitting.value = true
  2146. try {
  2147. const submitResult = await BoilerTaskOrderApi.submitReportAudit({
  2148. id: templateParams.value?.id,
  2149. auditor: reportAuditUserCode.value
  2150. })
  2151. if (submitResult) {
  2152. // 保存默认审核人(工号)
  2153. BoilerTaskOrderApi.saveDefaultApproval({
  2154. approveEmployeeNo: reportAuditUserCode.value
  2155. })
  2156. emit('template-confirm')
  2157. }
  2158. isShowReportAuditDialog.value = false
  2159. } finally {
  2160. reportAuditSubmitting.value = false
  2161. }
  2162. }
  2163. /**** 葡萄城:报告编制的显示和隐藏 end ****/
  2164. const handleModifyChecker = () => {
  2165. if (props.selectedItem) {
  2166. emit('modify-checker', props.selectedItem)
  2167. }
  2168. }
  2169. // 审批人选择确认
  2170. const handleApprovalSelectConfirm = async (res: any) => {
  2171. if (!props.selectedItem || !res || res.length === 0) {
  2172. ElMessage.warning('请选择审批人')
  2173. return
  2174. }
  2175. const ratifyId = res[0] // 获取选中的审批人ID
  2176. try {
  2177. await BoilerTaskOrderApi.submitReportApprove({
  2178. id: props.selectedItem.id,
  2179. ratifyId: ratifyId
  2180. })
  2181. ElMessage.success('提交审批成功')
  2182. isShowApprovalDialog.value = false
  2183. emit('submit-approval')
  2184. } catch (error: any) {
  2185. console.error('提交审批失败:', error)
  2186. ElMessage.error('提交审批失败,请稍后重试')
  2187. isShowApprovalDialog.value = false
  2188. }
  2189. }
  2190. // 加载流转记录列表
  2191. const recordList = ref<any[]>([])
  2192. const recordListLoading = ref(false)
  2193. const loadRecordList = async () => {
  2194. if (!props.selectedItem) return
  2195. try {
  2196. let response = null
  2197. // 检验意见通知书&重大问题线索
  2198. switch (props.selectedItem.reportType) {
  2199. case PressureReportType['SUGGUESTION']:
  2200. case PressureReportType['MAINQUESTION']:
  2201. response = await BoilerTaskOrderApi.getOpinionNoticeApprovalRecordList({
  2202. id: props.selectedItem.id
  2203. })
  2204. break
  2205. default:
  2206. // 其他
  2207. response = await BoilerTaskOrderApi.getTaskOrderItemReportRecordPage({
  2208. pageSize: 10,
  2209. pageNo: 1,
  2210. reportId: props.selectedItem.id
  2211. })
  2212. }
  2213. // 根据实际接口返回的数据结构获取数据
  2214. recordList.value = response?.data?.list || response?.list || response || []
  2215. } catch (error: any) {
  2216. console.error('获取流转记录失败:', error)
  2217. ElMessage.error('获取流转记录失败,请稍后重试')
  2218. recordList.value = []
  2219. } finally {
  2220. recordListLoading.value = false
  2221. }
  2222. }
  2223. // 获取报告字段纠错列表
  2224. const checkInputList = ref<any[]>([])
  2225. const handleGetCheckKeyInputs = async () => {
  2226. checkInputList.value = []
  2227. //if (!dataJson) return
  2228. if (!props.selectedItem?.id) return ElMessage.warning('字段纠错传参有误')
  2229. let reportId = props.selectedItem?.id
  2230. // const templateId = isShowReportPdf.value? props.selectedItem?.reportTemplateId : props.selectedItem?.templateId;
  2231. // if (isShowReportPdf.value){
  2232. // reportId = "report_" + reportId;
  2233. // }
  2234. let templateId = showReportPdfType.value === 'report' ? props.selectedItem?.reportTemplateId :
  2235. showReportPdfType.value === 'result' ? props.selectedItem?.resultTemplateId : props.selectedItem?.templateId;
  2236. if (showReportPdfType.value === 'report'){
  2237. reportId = "report_" + reportId;
  2238. }else if (showReportPdfType.value === 'result'){
  2239. reportId = "result_" + reportId;
  2240. }
  2241. const response = await BoilerTaskOrderApi.getCheckKeyIsInput(
  2242. { id: templateId,reportId: reportId },
  2243. JSON.parse("{}")
  2244. )
  2245. //console.log(JSON.parse(dataJson))
  2246. if (response) checkInputList.value = formatCheckInputList(response)
  2247. }
  2248. function formatCheckInputList(response, checkInputList = [] as any[]) {
  2249. if (!response) return []
  2250. for (let checkItem of response) {
  2251. if (!checkItem.child) {
  2252. checkInputList.push({ code: checkItem.code, name: checkItem.name })
  2253. }
  2254. if (is(checkItem.child, 'Array') && checkItem.type === 'object') {
  2255. formatCheckInputList(checkItem.child, checkInputList)
  2256. }
  2257. if (is(checkItem.child, 'Array') && checkItem.type === 'array') {
  2258. const childMapper = checkItem.child.map((item, i) => {
  2259. return {
  2260. code: `${checkItem.code}: ${item.code}`,
  2261. name: item.name
  2262. }
  2263. })
  2264. formatCheckInputList(childMapper, checkInputList)
  2265. }
  2266. }
  2267. return checkInputList
  2268. }
  2269. // 获取版本记录列表
  2270. const showVersionDetail = ref(false)
  2271. const historyList = ref<any[]>([])
  2272. async function getOrderHistoryVersion(id: string) {
  2273. try {
  2274. const response = await BoilerTaskOrderApi.getSafetyCheckRecordVersionPage({
  2275. pageNo: 1,
  2276. pageSize: 100,
  2277. businessType: 0,
  2278. orderItemReportId: id
  2279. })
  2280. historyList.value = response.list || []
  2281. } catch (error) {
  2282. console.error('获取历史版本失败:', error)
  2283. ElMessage.error('获取历史版本失败')
  2284. }
  2285. }
  2286. const versionDataCompareList = ref([])
  2287. const versionColumns = ref([
  2288. {
  2289. label: '字段名',
  2290. prop: 'displayName'
  2291. },
  2292. {
  2293. label: '修改前',
  2294. prop: 'oldValue',
  2295. render: (row, value) => (
  2296. <el-text link type="default">
  2297. {value || '-'}
  2298. </el-text>
  2299. )
  2300. },
  2301. {
  2302. label: '修改后',
  2303. prop: 'newValue',
  2304. render: (row, value) => <el-text type="primary">{value || '-'}</el-text>
  2305. }
  2306. ])
  2307. const versionDetailButtons = ref([
  2308. {
  2309. render: () => (
  2310. <el-button type="success" onClick={() => handleShowReportPdf()}>
  2311. 查看原文件PDF
  2312. </el-button>
  2313. )
  2314. }
  2315. ])
  2316. const showReportDialog = ref(false)
  2317. const currentUrl = ref('')
  2318. const reportSource = ref<any | null>(null)
  2319. const pdfLoading = ref(false)
  2320. // 恢复版本
  2321. const restoreVersion = (item: any) => {
  2322. ElMessageBox.confirm('是否确定恢复到该版本?', '提示', {
  2323. confirmButtonText: '确定',
  2324. cancelButtonText: '取消',
  2325. type: 'info',
  2326. center: true
  2327. })
  2328. .then(async () => {
  2329. const result = await BoilerTaskOrderApi.saveTaskReportTemplate({
  2330. id: props.selectedItem?.id,
  2331. reportUrl: item.oldReportUrl,
  2332. prepareJson: item.oldPrepareJson,
  2333. reportType: showReportPdfType.value,
  2334. modifiedReason: '恢复版本'
  2335. })
  2336. console.log('restoreVersion', result)
  2337. if (result) {
  2338. ElMessage({
  2339. type: 'success',
  2340. message: '操作成功'
  2341. })
  2342. handleRefresh()
  2343. } else {
  2344. ElMessage.warning('操作失败,请联系管理员!')
  2345. }
  2346. })
  2347. .catch(() => {})
  2348. }
  2349. // 查看版本详情的pdf
  2350. const handleShowReportPdf = async () => {
  2351. if (currentUrl.value) {
  2352. const reportId = props.selectedItem?.id;
  2353. reportInitData.value.refId = showReportPdfType.value === 'report' ? "report_" + reportId :
  2354. showReportPdfType.value === 'result' ? "result_" + reportId : reportId
  2355. reportInitData.value.templateId = showReportPdfType.value === 'report' ? templateParams.value.reportTemplateId :
  2356. showReportPdfType.value === 'result' ? templateParams.value.resultTemplateId : templateParams.value.templateId
  2357. reportInitData.value.dataSource = currentUrl.value.oldPrepareJson ? JSON.parse(currentUrl.value.oldPrepareJson) : {}
  2358. console.log(currentUrl)
  2359. showReportDialog.value = true
  2360. setTimeout(()=> {
  2361. reportSpreadRef.value?.reloadView();
  2362. })
  2363. pdfLoading.value = false
  2364. } else {
  2365. ElMessage.warning('报告缺失,请联系管理员!')
  2366. }
  2367. }
  2368. const handlePdfRendered = () => {
  2369. pdfLoading.value = false
  2370. currentUrl.value = ''
  2371. }
  2372. // 查看详情
  2373. const handleShowVersionInfo = (item) => {
  2374. showVersionDetail.value = true
  2375. currentUrl.value = item
  2376. versionDataCompareList.value = JSON.parse(item.modifiedObject)
  2377. }
  2378. // 检验项目添加上传附件功能
  2379. const reportListUploadModalRef = ref<InstanceType<typeof ReportListUploadModal>>()
  2380. const handleUploadItem = () => {
  2381. reportListUploadModalRef.value?.openModal(route.query.type === 'BoilerMyTask')
  2382. }
  2383. //切换记录和报告的显示
  2384. const handleChangeShowReportPdf = (showType: string) => {
  2385. if (showType === 'report' || showType === 'result'){
  2386. if (![PressureCheckerMyTaskStatus.REPORT_INPUT,PressureCheckerMyTaskStatus.REPORT_AUDIT,PressureCheckerMyTaskStatus.REPORT_APPROVE,PressureCheckerMyTaskStatus.REPORT_END].includes(props.selectedItem.taskStatus)){
  2387. ElMessage.error('该检验项目暂未生成报告!')
  2388. showReportPdfType.value = 'record';
  2389. return;
  2390. }
  2391. }
  2392. if (showType === 'result'){
  2393. if (!props.selectedItem?.resultTemplateId){
  2394. ElMessage.error('该检验项目没有结论报告!')
  2395. showReportPdfType.value = 'record';
  2396. return;
  2397. }
  2398. }
  2399. refreshPdfFlag.value = false;
  2400. showReportPdfType.value = showType;
  2401. handleRefreshData()
  2402. }
  2403. const isCanSyncReportData = computed(() => {
  2404. return true
  2405. //return [PressureCheckerMyTaskStatus.CONFIRMED,PressureCheckerMyTaskStatus.RECORD_INPUT].includes(props.selectedItem.taskStatus)
  2406. })
  2407. const handleSyncReportData = async () => {
  2408. try {
  2409. const response = await BoilerTaskOrderApi.syncReportData({
  2410. refId: props.selectedItem.id,
  2411. reportType : showReportPdfType.value
  2412. })
  2413. if (response){
  2414. ElMessage.success('同步数据成功')
  2415. handleRefresh()
  2416. }else{
  2417. ElMessage.error('同步数据失败,请稍后重试')
  2418. }
  2419. } catch (error: any) {
  2420. console.error('同步数据失败:', error)
  2421. ElMessage.error('同步数据失败,请稍后重试')
  2422. }
  2423. }
  2424. const showAssociationOperationManual = ref(false)
  2425. const handleShowAssociationOperationManual = () => {
  2426. showAssociationOperationManual.value = true
  2427. }
  2428. onMounted(() => {
  2429. console.log(props)
  2430. //initPreview()
  2431. BoilerTaskOrderApi.getAffairSummaryList().then((res) => {
  2432. oaSummaryList.value = res || []
  2433. })
  2434. })
  2435. const initPreviewTimer = ref<ReturnType<typeof setTimeout> | null>(null)
  2436. const initPreview=()=>{
  2437. // 清除上一次的定时器,防止快速切换项目时重复触发 reloadView
  2438. if (initPreviewTimer.value) {
  2439. clearTimeout(initPreviewTimer.value)
  2440. initPreviewTimer.value = null
  2441. }
  2442. const reportId = props.selectedItem?.id;
  2443. const refId = showReportPdfType.value === 'report' ? "report_" + reportId :
  2444. showReportPdfType.value === 'result' ? "result_" + reportId : reportId
  2445. const templateId = showReportPdfType.value === 'report' ? templateParams.value.reportTemplateId :
  2446. showReportPdfType.value === 'result' ? templateParams.value.resultTemplateId : templateParams.value.templateId
  2447. initData.value.templateId = templateId;
  2448. initData.value.refId = refId;
  2449. initData.value.refName = props.selectedItem?.reportName;
  2450. initData.value.opType = (props.selectedItem.taskStatus == 510 || props.selectedItem.taskStatus >= 600 || (showReportPdfType.value === 'record' && props.selectedItem.taskStatus > 500) || props?.selectedItem?.checkUsers?.[0]?.id != userStore?.user?.id || isSingleReportWithoutInstruction.value) ? 1 : 0;
  2451. if (props.selectedItem.reportType === PressureReportType['INSPECTIONPLAN'] && props.selectedItem.manualUrl){
  2452. initData.value.manualUrl = props.selectedItem.manualUrl;
  2453. }else{
  2454. initData.value.manualUrl = '';
  2455. }
  2456. // 记录模式使用 enterPdfUrl,报告模式使用 issueUrl,有值时跳过表格渲染直接预览 PDF
  2457. if (showReportPdfType.value === 'record') {
  2458. initData.value.enterPdfUrl = props.selectedItem?.enterPdfUrl || '';
  2459. initData.value.issueUrl = '';
  2460. } else {
  2461. initData.value.enterPdfUrl = '';
  2462. initData.value.issueUrl = props.selectedItem?.issueUrl || '';
  2463. }
  2464. initPreviewTimer.value = setTimeout(()=>{
  2465. initPreviewTimer.value = null
  2466. // 重新计算 PDF 区域宽度,确保不同屏幕下首次渲染时宽度正确
  2467. if (pdfPanelRef.value) {
  2468. const width = pdfPanelRef.value.clientWidth - 20
  2469. pdfContentWidth.value = props.isFullscreen ? width : (width > 1030 ? 1030 : width)
  2470. }
  2471. spreadRef.value?.reloadView();
  2472. },50)
  2473. console.log('initPreview', initData.value)
  2474. }
  2475. const editPreview=(reportType: string)=>{
  2476. const reportId = props.selectedItem?.id;
  2477. const refId = showReportPdfType.value === 'report' ? "report_" + reportId :
  2478. showReportPdfType.value === 'result' ? "result_" + reportId : reportId
  2479. const templateId = showReportPdfType.value === 'report' ? templateParams.value.reportTemplateId :
  2480. showReportPdfType.value === 'result' ? templateParams.value.resultTemplateId : templateParams.value.templateId
  2481. editData.value.templateId = templateId;
  2482. editData.value.refId = refId;
  2483. editData.value.opType = 0;
  2484. setTimeout(()=>{
  2485. if (reportType === 'record'){
  2486. editSpreadRecordRef.value?.reloadView();
  2487. }else if(reportType === 'report'){
  2488. editSpreadReportRef.value?.reloadView();
  2489. }
  2490. },50)
  2491. console.log('editDataPreview', editData.value)
  2492. }
  2493. const saveSuccess = async (data)=>{
  2494. if (props.selectedItem.taskStatus >= PressureCheckerMyTaskStatus.REPORT_INPUT){
  2495. saveSuccessReport(data)
  2496. }else {
  2497. saveSuccessRecord(data)
  2498. }
  2499. }
  2500. const saveSuccessRecord = async (data)=>{
  2501. const dataJson = !data.dataSource ? '' : JSON.stringify(data.dataSource)
  2502. const params = {
  2503. id: props.selectedItem?.id,
  2504. reportUrl: props.selectedItem?.id,
  2505. prepareJson: dataJson
  2506. }
  2507. const saveResult = await BoilerTaskOrderApi.saveTaskReportTemplate(params)
  2508. if (saveResult) {
  2509. showInlineEditCheckRecord.value = false
  2510. handleRefresh()
  2511. }
  2512. }
  2513. const handleClose = () => {
  2514. ElMessageBox.confirm('是否关闭?', {
  2515. confirmButtonText: '确认',
  2516. cancelButtonText: '取消',
  2517. type: 'warning',
  2518. }).then(() => {
  2519. showInlineEditCheckRecord.value = false
  2520. showInlineReportEdit.value = false
  2521. handleRefresh()
  2522. })
  2523. }
  2524. const saveSuccessReport = async (data)=>{
  2525. const dataJson = !data.dataSource ? '' : JSON.stringify(data.dataSource)
  2526. const saveResult = BoilerTaskOrderApi.saveReportPrepare({
  2527. id: props.selectedItem?.id,
  2528. prepareJson: dataJson,
  2529. })
  2530. if(saveResult) {
  2531. showInlineReportEdit.value = false
  2532. handleRefresh()
  2533. }
  2534. }
  2535. const handleRefreshData = () => {
  2536. loadRecordList()
  2537. initPreview()
  2538. handleGetCheckKeyInputs()
  2539. getOrderHistoryVersion(props.selectedItem?.id)
  2540. fetchProjectConfig()
  2541. }
  2542. // 应用项目 - 使用模板数据填充当前记录
  2543. const handleApplyTemplate = (defaultJson: Recordable) => {
  2544. if (!isCanEditTestRecord.value) {
  2545. return ElMessage.warning('非可编辑状态,不能应用模板')
  2546. }
  2547. // 将模板数据保存到 initData 的 dataSource 中,供 SpreadViewer 使用
  2548. initData.value = {
  2549. ...initData.value,
  2550. dataSource: defaultJson
  2551. }
  2552. setTimeout(()=>{
  2553. spreadRef.value?.reloadView();
  2554. },50)
  2555. console.log(initData);
  2556. }
  2557. // 更新模板 - 打开记录编辑器供用户更新模板
  2558. const handleUpdateTemplateUrl = (item: ReportItemVO) => {
  2559. handleEditSpreadRecord()
  2560. }
  2561. const getData = () =>{
  2562. return spreadRef.value?.getData()
  2563. }
  2564. defineExpose({
  2565. handleShowAssociationOperationManual,
  2566. handleApplyTemplate,
  2567. handleUpdateTemplateUrl,
  2568. getData,
  2569. handleUploadItem
  2570. })
  2571. //新加
  2572. // 添加控制展开/收缩状态的变量
  2573. const isExpanded = ref(false);
  2574. // 添加切换面板展开/收缩的方法
  2575. const togglePanel = () => {
  2576. isExpanded.value = !isExpanded.value;
  2577. setTimeout((() => {
  2578. nextTick(handleWindowResize)
  2579. }), 300);
  2580. };
  2581. // 获取PDF宽度
  2582. const pdfContentWidth = ref<number>(1100)
  2583. const pdfPanelRef = ref<HTMLDivElement>()
  2584. const handleWindowResize = debounce(() => {
  2585. if(!pdfPanelRef.value) return
  2586. const width = pdfPanelRef.value?.clientWidth - 20
  2587. pdfContentWidth.value = props.isFullscreen ? width : (width > 1030 ? 1030 : width)
  2588. //重新加载适应宽度
  2589. if (initData.value.opType == 1){
  2590. spreadRef.value?.reloadView();
  2591. }else {
  2592. spreadRef.value?.handleSave();
  2593. }
  2594. }, 100)
  2595. onMounted(() => {
  2596. handleWindowResize()
  2597. window.addEventListener('resize', handleWindowResize)
  2598. })
  2599. onUnmounted(() => {
  2600. window.removeEventListener('resize', handleWindowResize)
  2601. if (initPreviewTimer.value) {
  2602. clearTimeout(initPreviewTimer.value)
  2603. initPreviewTimer.value = null
  2604. }
  2605. })
  2606. // 全屏切换时重新计算PDF区域宽度
  2607. watch(() => props.isFullscreen, () => {
  2608. nextTick(() => {
  2609. handleWindowResize()
  2610. })
  2611. })
  2612. </script>
  2613. <style lang="scss" scoped>
  2614. // 批量提交校核弹窗样式
  2615. .batch-recheck-report-list {
  2616. width: 100%;
  2617. .batch-recheck-search {
  2618. margin-bottom: 8px;
  2619. }
  2620. .batch-recheck-header {
  2621. display: flex;
  2622. align-items: center;
  2623. justify-content: space-between;
  2624. padding: 6px 8px;
  2625. margin-bottom: 6px;
  2626. background: #f5f7fa;
  2627. border-radius: 4px;
  2628. .batch-recheck-count {
  2629. font-size: 12px;
  2630. color: #909399;
  2631. }
  2632. }
  2633. .batch-recheck-checkbox-group {
  2634. max-height: 260px;
  2635. overflow-y: auto;
  2636. padding: 4px 8px;
  2637. border: 1px solid #e4e7ed;
  2638. border-radius: 4px;
  2639. .batch-recheck-checkbox-item {
  2640. display: flex;
  2641. align-items: center;
  2642. width: 100%;
  2643. margin-right: 0;
  2644. padding: 6px 4px;
  2645. border-bottom: 1px dashed #ebeef5;
  2646. &:last-child {
  2647. border-bottom: none;
  2648. }
  2649. :deep(.el-checkbox__label) {
  2650. flex: 1;
  2651. overflow: hidden;
  2652. text-overflow: ellipsis;
  2653. white-space: nowrap;
  2654. }
  2655. }
  2656. }
  2657. }
  2658. .capsule-tabs {
  2659. display: inline-flex;
  2660. align-items: center;
  2661. gap: 2px;
  2662. padding: 2px;
  2663. border-radius: 20px;
  2664. margin-right: 12px;
  2665. flex-shrink: 0;
  2666. .tab-item {
  2667. display: inline-flex;
  2668. align-items: center;
  2669. justify-content: center;
  2670. padding: 4px 12px;
  2671. font-size: 12px;
  2672. background: white;
  2673. border-radius: 16px;
  2674. cursor: pointer;
  2675. transition: all 0.3s ease;
  2676. color: #606266;
  2677. font-weight: 500;
  2678. border: 1px solid transparent;
  2679. user-select: none;
  2680. white-space: nowrap;
  2681. min-width: 50px;
  2682. span {
  2683. font-size: 12px !important;
  2684. }
  2685. &:hover {
  2686. color: #409eff;
  2687. border-color: #409eff;
  2688. }
  2689. &.active {
  2690. background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
  2691. color: white;
  2692. border-color: #409eff;
  2693. box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
  2694. }
  2695. }
  2696. }
  2697. .container-panel {
  2698. height: 100%;
  2699. border: 1px solid var(--el-border-color);
  2700. .info-panel {
  2701. display: flex;
  2702. height: 32px;
  2703. padding: 0 8px 0 22px;
  2704. font-size: 14px;
  2705. color: #41475c;
  2706. background-color: #effaff;
  2707. justify-content: space-between;
  2708. align-items: center;
  2709. }
  2710. .status-operation-panel {
  2711. height: calc(100% - 32px);
  2712. .single-item-panel {
  2713. display: flex;
  2714. align-items: stretch;
  2715. justify-content: space-between;
  2716. width: 100%;
  2717. height: 100%;
  2718. }
  2719. .pdf-panel {
  2720. height: 100%;
  2721. padding: 10px;
  2722. flex: 1;
  2723. overflow: hidden;
  2724. }
  2725. .right-panel-container {
  2726. display: flex;
  2727. position: relative;
  2728. // 收缩展开按钮样式
  2729. .toggle-btn {
  2730. position: absolute;
  2731. left: -30px;
  2732. top: 50%;
  2733. transform: translateY(-50%);
  2734. width: 30px;
  2735. height: 60px;
  2736. background-color: #fff;
  2737. border: 1px solid var(--el-border-color);
  2738. border-radius: 10px 0 0 10px;
  2739. display: flex;
  2740. align-items: center;
  2741. justify-content: center;
  2742. cursor: pointer;
  2743. z-index: 100;
  2744. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  2745. transition: all 0.3s ease;
  2746. &:hover {
  2747. background-color: #f5f5f5;
  2748. }
  2749. &.collapsed {
  2750. left: -18px;
  2751. // border-radius: 0 10px 10px 0;
  2752. }
  2753. }
  2754. }
  2755. .operation-panel {
  2756. flex-basis: 300px;
  2757. padding: 10px 10px 10px 0;
  2758. box-sizing: border-box;
  2759. transition: all 0.3s ease;
  2760. // 展开状态样式
  2761. &.expanded {
  2762. width: 300px;
  2763. }
  2764. // 收缩状态样式
  2765. &.collapsed {
  2766. width: 0;
  2767. padding: 0;
  2768. border: none;
  2769. .default-toolbar,
  2770. .operation-inner {
  2771. display: none;
  2772. }
  2773. }
  2774. .operation-btns {
  2775. display: flex;
  2776. flex-wrap: wrap;
  2777. gap: 12px;
  2778. .el-button {
  2779. margin-bottom: 10px;
  2780. margin-left: 0;
  2781. }
  2782. }
  2783. }
  2784. .operation-inner {
  2785. height: 100%;
  2786. padding: 7px 10px 20px;
  2787. background-color: #fff;
  2788. border: 1px solid var(--el-border-color);
  2789. box-sizing: border-box;
  2790. }
  2791. .custom-inner{
  2792. display: flex;
  2793. flex-direction: column;
  2794. overflow: hidden;
  2795. height: 100%;
  2796. padding: 7px 10px 20px;
  2797. background-color: #fff;
  2798. border: 1px solid var(--el-border-color);
  2799. box-sizing: border-box;
  2800. .operation-item1 {
  2801. position: relative;
  2802. margin-bottom: 10px;
  2803. overflow-y: auto;
  2804. .item-header {
  2805. position: sticky;
  2806. top: 0;
  2807. left: 0;
  2808. z-index: 1000;
  2809. width: 100%;
  2810. height: 28px;
  2811. padding-left: 20px;
  2812. font-size: 16px;
  2813. line-height: 28px;
  2814. color: #fff;
  2815. background: #fff url('@/assets/imgs/pressure/my-task-detail-operation-bg.png') no-repeat
  2816. left top;
  2817. background-size: 100% 28px;
  2818. }
  2819. .item-content1 {
  2820. display: flex;
  2821. margin-top: 10px;
  2822. &-item {
  2823. font-size: 14px;
  2824. }
  2825. &-img {
  2826. flex: 1;
  2827. display: grid;
  2828. grid-template-columns: repeat(3, 1fr);
  2829. grid-gap: 10px;
  2830. .item-content1-img-item{
  2831. width: 100px;
  2832. height: 100px;
  2833. position: relative;
  2834. border-radius: 8px;
  2835. overflow: hidden;
  2836. cursor: pointer;
  2837. .video-play-overlay {
  2838. position: absolute;
  2839. top: 0;
  2840. left: 0;
  2841. width: 100%;
  2842. height: 100%;
  2843. border-radius: 8px;
  2844. transition: all 0.3s ease;
  2845. display: flex;
  2846. justify-content: center;
  2847. align-items: center;
  2848. background-color: rgba(0, 0, 0, 0.2);
  2849. color: #ffffffcc;
  2850. pointer-events: none;
  2851. &:hover {
  2852. background-color: rgba(0, 0, 0, 0.45);
  2853. color: #ffffff;
  2854. }
  2855. }
  2856. }
  2857. .img-item {
  2858. width: 100px;
  2859. height: 100px;
  2860. }
  2861. }
  2862. }
  2863. .record-item {
  2864. margin-bottom: 10px;
  2865. background-color: #f5f5fa;
  2866. border-radius: 2px;
  2867. }
  2868. .item-content {
  2869. padding: 10px 0;
  2870. font-size: 14px;
  2871. .el-empty {
  2872. width: 100%;
  2873. height: 150px;
  2874. padding: 0;
  2875. box-sizing: border-box;
  2876. }
  2877. &-item {
  2878. height: 32px;
  2879. display: flex;
  2880. align-items: center;
  2881. gap: 8px;
  2882. }
  2883. }
  2884. .record-item-title {
  2885. height: 31px;
  2886. padding: 0 13px;
  2887. line-height: 31px;
  2888. background: linear-gradient(270deg, rgb(128 176 251 / 20%) 0%, rgb(73 120 246 / 20%) 100%);
  2889. border-radius: 2px;
  2890. }
  2891. .record-item-inner {
  2892. padding: 11px 13px 8px;
  2893. .content {
  2894. display: flex;
  2895. align-items: center;
  2896. .el-button {
  2897. margin-left: 8px;
  2898. }
  2899. :deep(.el-button--small.is-round) {
  2900. height: auto;
  2901. padding: 3px 11px;
  2902. }
  2903. .time {
  2904. color: rgb(31 31 31 / 88%);
  2905. text-align: right;
  2906. opacity: 0.5;
  2907. flex: 1;
  2908. }
  2909. }
  2910. .desc {
  2911. margin-top: 7px;
  2912. color: #41475c;
  2913. opacity: 0.8;
  2914. span {
  2915. opacity: 0.5;
  2916. }
  2917. }
  2918. }
  2919. .error-item {
  2920. display: flex;
  2921. padding: 7px 10px;
  2922. color: #41475c;
  2923. background-color: #f5f5fa;
  2924. border: 1px solid var(--el-border-color);
  2925. border-width: 1px 0;
  2926. align-items: center;
  2927. .el-icon {
  2928. margin-right: 6px;
  2929. }
  2930. &:nth-child(n + 1) {
  2931. margin-top: -1px;
  2932. }
  2933. &:nth-child(even) {
  2934. background-color: #fff;
  2935. }
  2936. }
  2937. .history-item {
  2938. padding: 7px 10px 10px;
  2939. margin-bottom: 13px;
  2940. color: #41475c;
  2941. background-color: #f5f5fa;
  2942. border-radius: 2px;
  2943. .history-title {
  2944. display: flex;
  2945. align-items: center;
  2946. margin-left: -10px;
  2947. font-weight: 600;
  2948. line-height: 20px;
  2949. &::before {
  2950. display: block;
  2951. width: 4px;
  2952. height: 15px;
  2953. margin-right: 7px;
  2954. background: #4978f6;
  2955. content: '';
  2956. }
  2957. }
  2958. p {
  2959. margin: 10px 0;
  2960. line-height: 20px;
  2961. }
  2962. .history-footer {
  2963. display: flex;
  2964. // align-items: center;
  2965. font-size: 12px;
  2966. .name {
  2967. color: #4978f6;
  2968. }
  2969. .time {
  2970. margin-left: 2px;
  2971. color: rgb(65 71 92 / 50%);
  2972. }
  2973. div {
  2974. flex: 1;
  2975. display: flex;
  2976. justify-content: flex-end;
  2977. .el-button span {
  2978. font-size: 12px;
  2979. }
  2980. }
  2981. }
  2982. }
  2983. }
  2984. .videoAndImg {
  2985. flex: 1;
  2986. width: 100%;
  2987. overflow-y: auto;
  2988. }
  2989. }
  2990. .operation-item {
  2991. position: relative;
  2992. max-height: calc(100% / 3 - 10px);
  2993. margin-bottom: 10px;
  2994. overflow-y: auto;
  2995. .item-header {
  2996. position: sticky;
  2997. top: 0;
  2998. left: 0;
  2999. z-index: 1000;
  3000. width: 100%;
  3001. height: 28px;
  3002. padding-left: 20px;
  3003. font-size: 16px;
  3004. line-height: 28px;
  3005. color: #fff;
  3006. background: #fff url('@/assets/imgs/pressure/my-task-detail-operation-bg.png') no-repeat
  3007. left top;
  3008. background-size: 100% 28px;
  3009. }
  3010. .item-content1 {
  3011. display: flex;
  3012. margin-top: 10px;
  3013. &-item {
  3014. font-size: 14px;
  3015. }
  3016. &-img {
  3017. flex: 1;
  3018. display: grid;
  3019. grid-template-columns: repeat(3, 1fr);
  3020. grid-gap: 10px;
  3021. .item-content1-img-item{
  3022. width: 100px;
  3023. height: 100px;
  3024. position: relative;
  3025. border-radius: 8px;
  3026. overflow: hidden;
  3027. cursor: pointer;
  3028. .video-play-overlay {
  3029. position: absolute;
  3030. top: 0;
  3031. left: 0;
  3032. width: 100%;
  3033. height: 100%;
  3034. border-radius: 8px;
  3035. transition: all 0.3s ease;
  3036. display: flex;
  3037. justify-content: center;
  3038. align-items: center;
  3039. background-color: rgba(0, 0, 0, 0.2);
  3040. color: #ffffffcc;
  3041. pointer-events: none;
  3042. &:hover {
  3043. background-color: rgba(0, 0, 0, 0.45);
  3044. color: #ffffff;
  3045. }
  3046. }
  3047. }
  3048. .img-item {
  3049. width: 100px;
  3050. height: 100px;
  3051. }
  3052. }
  3053. }
  3054. .record-item {
  3055. margin-bottom: 10px;
  3056. background-color: #f5f5fa;
  3057. border-radius: 2px;
  3058. }
  3059. .item-content {
  3060. padding: 10px 0;
  3061. font-size: 14px;
  3062. .el-empty {
  3063. width: 100%;
  3064. height: 150px;
  3065. padding: 0;
  3066. box-sizing: border-box;
  3067. }
  3068. &-item {
  3069. height: 32px;
  3070. display: flex;
  3071. align-items: center;
  3072. gap: 8px;
  3073. }
  3074. }
  3075. .record-item-title {
  3076. height: 31px;
  3077. padding: 0 13px;
  3078. line-height: 31px;
  3079. background: linear-gradient(270deg, rgb(128 176 251 / 20%) 0%, rgb(73 120 246 / 20%) 100%);
  3080. border-radius: 2px;
  3081. }
  3082. .record-item-inner {
  3083. padding: 11px 13px 8px;
  3084. .content {
  3085. display: flex;
  3086. align-items: center;
  3087. .el-button {
  3088. margin-left: 8px;
  3089. }
  3090. :deep(.el-button--small.is-round) {
  3091. height: auto;
  3092. padding: 3px 11px;
  3093. }
  3094. .time {
  3095. color: rgb(31 31 31 / 88%);
  3096. text-align: right;
  3097. opacity: 0.5;
  3098. flex: 1;
  3099. }
  3100. }
  3101. .desc {
  3102. margin-top: 7px;
  3103. color: #41475c;
  3104. opacity: 0.8;
  3105. span {
  3106. opacity: 0.5;
  3107. }
  3108. }
  3109. }
  3110. .error-item {
  3111. display: flex;
  3112. padding: 7px 10px;
  3113. color: #41475c;
  3114. background-color: #f5f5fa;
  3115. border: 1px solid var(--el-border-color);
  3116. border-width: 1px 0;
  3117. align-items: center;
  3118. .el-icon {
  3119. margin-right: 6px;
  3120. }
  3121. &:nth-child(n + 1) {
  3122. margin-top: -1px;
  3123. }
  3124. &:nth-child(even) {
  3125. background-color: #fff;
  3126. }
  3127. }
  3128. .history-item {
  3129. padding: 7px 10px 10px;
  3130. margin-bottom: 13px;
  3131. color: #41475c;
  3132. background-color: #f5f5fa;
  3133. border-radius: 2px;
  3134. .history-title {
  3135. display: flex;
  3136. align-items: center;
  3137. margin-left: -10px;
  3138. font-weight: 600;
  3139. line-height: 20px;
  3140. &::before {
  3141. display: block;
  3142. width: 4px;
  3143. height: 15px;
  3144. margin-right: 7px;
  3145. background: #4978f6;
  3146. content: '';
  3147. }
  3148. }
  3149. p {
  3150. margin: 10px 0;
  3151. line-height: 20px;
  3152. }
  3153. .history-footer {
  3154. display: flex;
  3155. // align-items: center;
  3156. font-size: 12px;
  3157. .name {
  3158. color: #4978f6;
  3159. }
  3160. .time {
  3161. margin-left: 2px;
  3162. color: rgb(65 71 92 / 50%);
  3163. }
  3164. div {
  3165. flex: 1;
  3166. display: flex;
  3167. justify-content: flex-end;
  3168. .el-button span {
  3169. font-size: 12px;
  3170. }
  3171. }
  3172. }
  3173. }
  3174. }
  3175. }
  3176. }
  3177. .default-toolbar {
  3178. display: flex;
  3179. justify-content: flex-end;
  3180. gap: 16px;
  3181. white-space: nowrap;
  3182. ::v-deep .el-button {
  3183. margin-left: 0;
  3184. }
  3185. }
  3186. // 退回对话框样式
  3187. .reject-dialog-content {
  3188. .form-item {
  3189. margin-bottom: 20px;
  3190. &:last-child {
  3191. margin-bottom: 0;
  3192. }
  3193. .form-label {
  3194. display: block;
  3195. margin-bottom: 8px;
  3196. font-size: 14px;
  3197. font-weight: 500;
  3198. color: #606266;
  3199. }
  3200. }
  3201. }
  3202. :deep(.preview-dialog) {
  3203. background-color: transparent !important;
  3204. width: auto !important;
  3205. padding: 0 !important;
  3206. .el-dialog__header {
  3207. display: none;
  3208. }
  3209. .el-dialog__body{
  3210. padding: 0;
  3211. .preview-container{
  3212. padding: 0;
  3213. position: relative;
  3214. }
  3215. .preview-image,.preview-video {
  3216. height: 65vh;
  3217. max-width: 100vw;
  3218. }
  3219. }
  3220. }
  3221. .rectification-dialog {
  3222. padding: 8px 0;
  3223. .upload-section {
  3224. margin-bottom: 24px;
  3225. padding: 20px;
  3226. background: #fafafa;
  3227. border-radius: 8px;
  3228. transition: all 0.3s ease;
  3229. &:hover {
  3230. background: #f5f5f5;
  3231. }
  3232. &.required {
  3233. border: 1px solid #e8f4ff;
  3234. background: #f7fbff;
  3235. &:hover {
  3236. background: #f0f8ff;
  3237. }
  3238. }
  3239. .section-header {
  3240. display: flex;
  3241. align-items: center;
  3242. justify-content: space-between;
  3243. margin-bottom: 16px;
  3244. .header-left {
  3245. display: flex;
  3246. align-items: center;
  3247. gap: 8px;
  3248. .section-title {
  3249. font-size: 15px;
  3250. font-weight: 600;
  3251. color: #303133;
  3252. }
  3253. .required-mark {
  3254. color: #f56c6c;
  3255. font-size: 16px;
  3256. font-weight: bold;
  3257. }
  3258. i[class^="icon-"] {
  3259. font-size: 18px;
  3260. color: #409eff;
  3261. }
  3262. }
  3263. .section-tip {
  3264. font-size: 12px;
  3265. color: #909399;
  3266. background: #fff;
  3267. padding: 4px 12px;
  3268. border-radius: 12px;
  3269. border: 1px solid #e4e7ed;
  3270. }
  3271. }
  3272. .upload-area {
  3273. min-height: 80px;
  3274. }
  3275. }
  3276. .info-tips {
  3277. display: flex;
  3278. gap: 12px;
  3279. padding: 16px;
  3280. background: linear-gradient(135deg, #fff9e6 0%, #fff5cc 100%);
  3281. border-left: 4px solid #e6a23c;
  3282. border-radius: 6px;
  3283. margin-top: 20px;
  3284. .icon-info {
  3285. font-size: 20px;
  3286. color: #e6a23c;
  3287. flex-shrink: 0;
  3288. margin-top: 2px;
  3289. }
  3290. .tips-content {
  3291. flex: 1;
  3292. .tips-title {
  3293. font-weight: 600;
  3294. color: #606266;
  3295. margin-bottom: 8px;
  3296. font-size: 14px;
  3297. }
  3298. p {
  3299. font-size: 13px;
  3300. color: #606266;
  3301. line-height: 1.8;
  3302. margin: 0;
  3303. &:not(.tips-title) {
  3304. padding-left: 12px;
  3305. position: relative;
  3306. &::before {
  3307. content: "•";
  3308. position: absolute;
  3309. left: 0;
  3310. color: #e6a23c;
  3311. }
  3312. }
  3313. }
  3314. }
  3315. }
  3316. }
  3317. .icon-document::before {
  3318. content: "📄";
  3319. }
  3320. .icon-image::before {
  3321. content: "🖼️";
  3322. }
  3323. .icon-video::before {
  3324. content: "🎬";
  3325. }
  3326. .icon-info::before {
  3327. content: "ℹ️";
  3328. }
  3329. .dialog-content {
  3330. font-size: 15px;
  3331. line-height: 30px;
  3332. display: flex;
  3333. justify-content: center;
  3334. align-items: center;
  3335. height: 100%;
  3336. }
  3337. </style>