StatusOperationPanel.vue 112 KB

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