TaskOnlineEquipmentList.vue 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350
  1. <route lang="json5" type="page">
  2. {
  3. layout: 'default',
  4. style: {
  5. navigationBarTitleText: '设备列表(分配项目)',
  6. navigationStyle: 'custom',
  7. disableScroll: true,
  8. 'app-plus': {
  9. bounce: 'none',
  10. },
  11. },
  12. }
  13. </route>
  14. <template>
  15. <view class="equipment-list-container">
  16. <NavBar title="设备列表(分配项目)" />
  17. <scroll-view
  18. class="list-scroll"
  19. scroll-y
  20. refresher-enabled
  21. :refresher-triggered="refreshing"
  22. @refresherrefresh="onRefresh"
  23. >
  24. <view v-for="item in equipmentList" :key="item.id" class="equipment-cell">
  25. <view class="cell-top" @click="handleSelectEquipment(item)">
  26. <view class="row">
  27. <view v-if="orderId" class="checkbox-wrapper">
  28. <view class="checkbox" :class="{ checked: selectedMap[item.id] }">
  29. <image
  30. v-if="selectedMap[item.id]"
  31. class="check-icon"
  32. src="/static/images/white-check.png"
  33. mode="aspectFit"
  34. />
  35. </view>
  36. </view>
  37. <view class="cell-top-left">
  38. <text class="equip-name">{{ item.equipName }}</text>
  39. <text class="main-checker">主检人:{{ item?.mainCheckerUser?.nickname || '' }}</text>
  40. <view
  41. class="status-tag"
  42. :class="
  43. item?.taskStatus !== PressureCheckerMyTaskStatus.CONFIRMED
  44. ? 'status-pending'
  45. : 'status-done'
  46. "
  47. >
  48. <text class="status-text">
  49. {{ PressureCheckerMyTaskStatusMap[item?.taskStatus] }}
  50. </text>
  51. </view>
  52. </view>
  53. </view>
  54. </view>
  55. <view class="cell-center" @click="handleRouteToEquipmentDetail(item, 'EQUIPMENT')">
  56. <view class="info-box">
  57. <text class="info-label">
  58. 设备注册代码:
  59. <text class="info-value">{{ item.equipCode }}</text>
  60. </text>
  61. <text class="info-label">
  62. 费用:
  63. <text class="info-value">{{ handleCalcTotalFee(item?.reportDOList) }}</text>
  64. </text>
  65. </view>
  66. </view>
  67. <view class="cell-bottom">
  68. <view
  69. v-if="
  70. !item.mainChecker == null &&
  71. item.taskStatus === PressureCheckerMyTaskStatus.RECORD_INPUT
  72. "
  73. class="claim-btn white-btn"
  74. @click="fetchClaimEquipments(item.id, item.mainChecker != null)"
  75. >
  76. <text class="white-btn-text">认领</text>
  77. </view>
  78. <view
  79. v-if="item.mainChecker != null"
  80. class="record-btn blue-btn"
  81. @click="handleRouteToEquipmentDetail(item, 'INSPECT')"
  82. >
  83. <text class="blue-btn-text">记录录入</text>
  84. </view>
  85. </view>
  86. </view>
  87. <view v-if="loading && equipmentList.length === 0" class="loading-text">加载中...</view>
  88. <view v-if="!loading && equipmentList.length === 0" class="empty-text">
  89. <text>暂无数据</text>
  90. </view>
  91. </scroll-view>
  92. <view v-if="orderId" class="bottom-operate">
  93. <view class="select-all" @click="handleSelectAll">
  94. <view class="checkbox" :class="{ checked: selectAll }">
  95. <image
  96. v-if="selectAll"
  97. class="check-icon"
  98. src="/static/images/white-check.png"
  99. mode="aspectFit"
  100. />
  101. </view>
  102. <text class="select-all-text">全选</text>
  103. </view>
  104. <view class="btn-group">
  105. <button
  106. class="operate-btn blue-btn"
  107. :style="!selectedEquipments.length ? { opacity: 0.5 } : {}"
  108. @click="selectedEquipments.length && (showMoreOperate = true)"
  109. >
  110. 更多操作
  111. </button>
  112. <button
  113. class="operate-btn white-btn"
  114. :style="!selectedEquipments.length ? { opacity: 0.5 } : {}"
  115. @click="
  116. selectedEquipments.length &&
  117. showBatchClaimPopup({ text: '是否认领已选择的设备?', isClaim: false })
  118. "
  119. >
  120. 批量认领
  121. </button>
  122. <button
  123. class="operate-btn blue-btn"
  124. :style="!selectedEquipments.length ? { opacity: 0.5 } : {}"
  125. @click="selectedEquipments.length && showCheckProjectPopup()"
  126. >
  127. 添加项目
  128. </button>
  129. </view>
  130. </view>
  131. <view v-if="showTipsPopup" class="popup-mask" @click="closeTipsPopup">
  132. <view class="popup-content tips-popup" @click.stop>
  133. <text class="tips-text">{{ tipsPopupData.text }}</text>
  134. <view class="popup-actions">
  135. <button class="action-btn cancel-btn" @click="closeTipsPopup">取消</button>
  136. <button class="action-btn confirm-btn" @click="handleBatchClaim">确定</button>
  137. </view>
  138. </view>
  139. </view>
  140. <view v-if="showMoreOperate" class="more-operate-overlay" @click="showMoreOperate = false">
  141. <view class="more-operate-panel" :class="{ 'more-panel-show': showMoreOperate }">
  142. <view
  143. class="more-btn-item"
  144. :class="{ disabled: !canInform }"
  145. @click="canInform && createInform()"
  146. >
  147. <view class="more-btn-inner more-btn-border">
  148. <text class="more-btn-text" :style="{ color: canInform ? 'rgb(51,51,51)' : '#ccc' }">
  149. 重大问题线索
  150. </text>
  151. </view>
  152. </view>
  153. <view
  154. class="more-btn-item"
  155. :class="{ disabled: !canSuspend }"
  156. @click="canSuspend && showSuspendPopupFunc()"
  157. >
  158. <view class="more-btn-inner more-btn-border">
  159. <text class="more-btn-text" :style="{ color: canSuspend ? 'rgb(51,51,51)' : '#ccc' }">
  160. 客户拒检
  161. </text>
  162. </view>
  163. </view>
  164. <view
  165. class="more-btn-item"
  166. :class="{ disabled: !canAddInspectionplan }"
  167. @click="canAddInspectionplan && showAddInspectionplanPopup()"
  168. >
  169. <view class="more-btn-inner more-btn-border">
  170. <text
  171. class="more-btn-text"
  172. :style="{ color: canAddInspectionplan ? 'rgb(51,51,51)' : '#ccc' }"
  173. >
  174. 检验方案
  175. </text>
  176. </view>
  177. </view>
  178. <view
  179. class="more-btn-item"
  180. :class="{ disabled: !canUpdateContact }"
  181. @click="canUpdateContact && handleUpdateContact()"
  182. >
  183. <view class="more-btn-inner more-btn-border">
  184. <text
  185. class="more-btn-text"
  186. :style="{ color: canUpdateContact ? 'rgb(51,51,51)' : '#ccc' }"
  187. >
  188. 修改安全管理员
  189. </text>
  190. </view>
  191. </view>
  192. </view>
  193. </view>
  194. <view v-if="showSuspendPopup" class="popup-mask" @click="closeSuspendPopup">
  195. <view class="popup-content suspend-popup" @click.stop>
  196. <text class="popup-title">客户拒检</text>
  197. <view class="form-item">
  198. <text class="form-label">拒检类别</text>
  199. <wd-picker
  200. v-model="suspendReasonDict"
  201. :columns="refuseCategoryColumns"
  202. placeholder="请选择拒检类别"
  203. />
  204. </view>
  205. <textarea
  206. v-if="suspendReasonDict === '5'"
  207. v-model="suspendReason"
  208. class="suspend-textarea"
  209. placeholder="请输入拒绝检验原因"
  210. />
  211. <view class="popup-actions suspend-actions">
  212. <button class="action-btn cancel-btn" @click="closeSuspendPopup">取消</button>
  213. <button class="action-btn confirm-btn" @click="suspendCheck(1)">上报市局</button>
  214. <button class="action-btn warn-btn" @click="suspendCheck(0)">无需上报</button>
  215. </view>
  216. </view>
  217. </view>
  218. <view v-if="showInspectionplanPopup" class="popup-mask" @click="closeInspectionplanPopup">
  219. <view class="popup-content inspectionplan-popup" @click.stop>
  220. <text class="popup-title">添加检验方案</text>
  221. <view class="tab-row">
  222. <view
  223. :class="['tab-item', { active: inspectionPlanType === 'commPlan' }]"
  224. @click="inspectionPlanType = 'commPlan'"
  225. >
  226. 通用
  227. </view>
  228. <view
  229. :class="['tab-item', { active: inspectionPlanType === 'selfPlan' }]"
  230. @click="inspectionPlanType = 'selfPlan'"
  231. >
  232. 自编
  233. </view>
  234. </view>
  235. <view class="form-item">
  236. <text class="form-label">{{ inspectionPlanType === 'commPlan' ? '检验方案' : '模板封面' }}</text>
  237. <wd-picker
  238. v-model="selectedTemplateId"
  239. :columns="templateListColumn"
  240. @confirm="onTemplateChange"
  241. />
  242. </view>
  243. <view class="form-item">
  244. <text class="form-label">检验方案名称</text>
  245. <input v-model="inspectionplanName" class="form-input" placeholder="请输入检验方案名称" />
  246. </view>
  247. <view class="popup-actions">
  248. <button class="action-btn cancel-btn" @click="closeInspectionplanPopup">取消</button>
  249. <button class="action-btn confirm-btn" @click="addInspectionplanConfirm">确定</button>
  250. </view>
  251. </view>
  252. </view>
  253. <UpdateSafetyManagerPopup
  254. v-if="showUpdateContactPopup"
  255. :safe-manager="currentSafeManager.name"
  256. :safe-manager-phone="currentSafeManager.phone"
  257. @hide="showUpdateContactPopup = false"
  258. @confirm="handleUpdateSafetyManagerConfirm"
  259. />
  260. <view
  261. v-if="showCheckProject"
  262. class="popup-mask"
  263. @click="closeCheckProjectPopup"
  264. @touchmove.stop.prevent
  265. >
  266. <view class="popup-content check-project-popup" @click.stop @touchmove.stop>
  267. <view class="popup-header">
  268. <text class="popup-title">添加检验项目</text>
  269. <text class="popup-close" @click="closeCheckProjectPopup">✕</text>
  270. </view>
  271. <BoilerCheckProject
  272. v-if="showCheckProject"
  273. :propject-list="checkProjectList"
  274. :select-templates="selectTemplates"
  275. use-online="1"
  276. :equip-data="equipDataForPopup"
  277. @change="handleCheckProjectChange"
  278. @confirm="handleCheckProjectConfirm"
  279. @cancel="closeCheckProjectPopup"
  280. />
  281. </view>
  282. </view>
  283. </view>
  284. </template>
  285. <script lang="ts" setup>
  286. import { ref, computed } from 'vue'
  287. import { onLoad, onShow } from '@dcloudio/uni-app'
  288. import { pressure2NotVerifyPageApi } from '@/api/task'
  289. import { useUserStore } from '@/store/user'
  290. import { useConfigStore } from '@/store/config'
  291. import {
  292. PressureCheckerMyTaskStatus,
  293. PressureCheckerMyTaskStatusMap,
  294. getStrDictOptions,
  295. } from '@/utils/dictMap'
  296. import dayjs from 'dayjs'
  297. import UpdateSafetyManagerPopup from '@/pages/unClaim/components/UpdateSafetyManagerPopup.vue'
  298. import { TaskOrderFuncName, requestFunc } from '@/api/ApiRouter/taskOrder'
  299. import {
  300. getBoilerTaskItemListByOrderId,
  301. addInspectProject,
  302. confirmBoilerEquipmentBatchClaim,
  303. } from '@/api/boiler/boilerTaskOrder'
  304. import { updateEquipBoilerSecurityManager } from '@/api/boiler/boilerEquip'
  305. import NavBar from '@/components/NavBar/NavBar.vue'
  306. import BoilerCheckProject from '@/pages/taskOnline/components/BoilerCheckProject.vue'
  307. interface PopupData {
  308. text: string
  309. isClaim: boolean
  310. }
  311. defineOptions({ name: 'TaskOnlineEquipmentList' })
  312. const userStore = useUserStore()
  313. const userInfo = computed(() => userStore.userInfo)
  314. const equipType = useConfigStore().getEquipType()
  315. const orderId = ref('')
  316. const orderNo = ref('')
  317. const equipmentList = ref<any[]>([])
  318. const loading = ref(false)
  319. const refreshing = ref(false)
  320. const selectAll = ref(false)
  321. const selectedEquipments = ref<any[]>([])
  322. const selectedMap = ref<Record<string, boolean>>({})
  323. const canInform = ref(false)
  324. const canSuspend = ref(false)
  325. const canAddInspectionplan = ref(false)
  326. const canUpdateContact = ref(false)
  327. const showTipsPopup = ref(false)
  328. const tipsPopupData = ref<PopupData>({ text: '', isClaim: false })
  329. const showMoreOperate = ref(false)
  330. const showSuspendPopup = ref(false)
  331. const suspendReason = ref('')
  332. const suspendReasonDict = ref('')
  333. const refuseCategoryColumns = computed(() => {
  334. return getStrDictOptions('refuseInspectedCategory').map((item: any) => ({
  335. label: item.label,
  336. value: item.value,
  337. }))
  338. })
  339. const showInspectionplanPopup = ref(false)
  340. const inspectionPlanType = ref('commPlan')
  341. const inspectionplanName = ref('')
  342. const selectedTemplate = ref<any>(null)
  343. const selectedTemplateId = ref('')
  344. const templateList = ref<any[]>([])
  345. const optionsResult = ref<any[]>([])
  346. const filteredTemplateList = computed(() => {
  347. if (inspectionPlanType.value === 'commPlan') {
  348. return optionsResult.value.filter(
  349. (item) => item.reportType === 900 && (item.pjType === 3 || item.pjType === 5),
  350. )
  351. } else if (inspectionPlanType.value === 'selfPlan') {
  352. return optionsResult.value.filter(
  353. (item) => item.reportType === 500 && item.pjType === 5,
  354. )
  355. }
  356. return []
  357. })
  358. const templateListColumn = computed(() => [
  359. filteredTemplateList.value.map((item) => ({ label: item.tbName, value: item.id })),
  360. ])
  361. const showUpdateContactPopup = ref(false)
  362. const currentItem = ref<any>({})
  363. const showCheckProject = ref(false)
  364. const checkProjectList = ref<any[][]>([])
  365. const selectTemplates = ref<Record<string, any[]>>({})
  366. const currentSelectedCheckProjectItems = ref<any[]>([])
  367. const equipDataForPopup = ref<any>({})
  368. const currentSafeManager = computed(() => {
  369. return {
  370. name: currentItem.value?.safery || '',
  371. phone: currentItem.value?.saferydh || '',
  372. }
  373. })
  374. onLoad((options: any) => {
  375. orderId.value = options?.orderId || ''
  376. orderNo.value = options?.orderNo || ''
  377. })
  378. onShow(() => {
  379. fetchCheckerOnlineEquipmentList()
  380. })
  381. const selectedEquipIds = computed(() => selectedEquipments.value.map((item) => item.equipId))
  382. const fetchCheckerOnlineEquipmentList = async () => {
  383. loading.value = true
  384. try {
  385. const res = await getBoilerTaskItemListByOrderId({ id: orderId.value })
  386. equipmentList.value = res?.data?.orderItems || []
  387. } catch (error) {
  388. console.error('获取设备列表失败:', error)
  389. } finally {
  390. loading.value = false
  391. }
  392. }
  393. const onRefresh = async () => {
  394. refreshing.value = true
  395. await fetchCheckerOnlineEquipmentList()
  396. refreshing.value = false
  397. }
  398. const refreshList = () => {
  399. fetchCheckerOnlineEquipmentList()
  400. }
  401. const handleSelectEquipment = (item: any) => {
  402. if (!orderId.value) return
  403. const isSelected = !selectedMap.value[item.id]
  404. selectedMap.value[item.id] = isSelected
  405. if (isSelected) {
  406. selectedEquipments.value.push(item)
  407. } else {
  408. selectedEquipments.value = selectedEquipments.value.filter((ele) => ele.id !== item.id)
  409. }
  410. updateOperateStatus()
  411. selectAll.value = selectedEquipments.value.length === equipmentList.value.length
  412. }
  413. const handleSelectAll = () => {
  414. const newSelectAll = !selectAll.value
  415. selectAll.value = newSelectAll
  416. selectedEquipments.value = []
  417. const newMap: Record<string, boolean> = {}
  418. for (const item of equipmentList.value) {
  419. newMap[item.id] = newSelectAll
  420. if (newSelectAll) {
  421. selectedEquipments.value.push(item)
  422. }
  423. }
  424. selectedMap.value = newMap
  425. updateOperateStatus()
  426. }
  427. const updateOperateStatus = () => {
  428. canInform.value = selectedEquipments.value.length === 1
  429. canAddInspectionplan.value = selectedEquipments.value.length >= 1
  430. canUpdateContact.value = selectedEquipments.value.length === 1
  431. canSuspend.value = selectedEquipments.value.length >= 1
  432. }
  433. const initSelect = () => {
  434. selectedEquipments.value = []
  435. selectedMap.value = {}
  436. selectAll.value = false
  437. updateOperateStatus()
  438. }
  439. const showBatchClaimPopup = (popupData: PopupData) => {
  440. const networkType = uni.getNetworkTypeSync?.()
  441. if (networkType === 'none') {
  442. return uni.showToast({ title: '暂无网络无法认领,请联网后认领设备', icon: 'error' })
  443. }
  444. if (!selectedEquipments.value.length) {
  445. return uni.showToast({
  446. title: `请选择要批量${popupData.isClaim ? '取消认领' : '认领'}的设备`,
  447. icon: 'error',
  448. })
  449. }
  450. tipsPopupData.value = popupData
  451. showTipsPopup.value = true
  452. }
  453. const closeTipsPopup = () => {
  454. showTipsPopup.value = false
  455. }
  456. const handleBatchClaim = async () => {
  457. closeTipsPopup()
  458. const ids = selectedEquipments.value.map((item: any) => item.mainID)
  459. const params: { orderItemIdList: string[] } = { orderItemIdList: ids }
  460. try {
  461. const result = await confirmBoilerEquipmentBatchClaim(params)
  462. updateClaim(result, ids, !tipsPopupData.value.isClaim)
  463. } catch (error) {
  464. uni.showToast({ title: '操作失败', icon: 'error' })
  465. }
  466. }
  467. const fetchClaimEquipments = async (equipmentId: string, isClaim: boolean) => {
  468. const networkType = uni.getNetworkTypeSync?.()
  469. if (networkType === 'none') {
  470. return uni.showToast({ title: '暂无网络无法认领,请联网后认领设备', icon: 'error' })
  471. }
  472. const currentUserInfo = userInfo.value
  473. if (!currentUserInfo) {
  474. return uni.redirectTo({ url: '/pages/login/login' })
  475. }
  476. try {
  477. let result: any
  478. if (isClaim) {
  479. result = await requestFunc(TaskOrderFuncName.EquipmentCancelClaim, equipType, {
  480. id: equipmentId,
  481. })
  482. } else {
  483. result = await requestFunc(TaskOrderFuncName.EquipmentConfirmClaim, equipType, {
  484. id: equipmentId,
  485. })
  486. }
  487. updateClaim(result, [equipmentId], !isClaim)
  488. } catch (error) {
  489. uni.showToast({ title: '操作失败', icon: 'error' })
  490. }
  491. }
  492. const updateClaim = async (result: any, equipmentIds: string[], isClaim: boolean) => {
  493. if (result?.code === 0 && result?.data === true) {
  494. uni.showToast({ title: `${isClaim ? '认领' : '取消认领'}成功` })
  495. initSelect()
  496. await fetchCheckerOnlineEquipmentList()
  497. } else {
  498. const msg = result?.msg || `${isClaim ? '认领' : '取消认领'}失败`
  499. uni.showToast({ title: msg, icon: 'error' })
  500. }
  501. }
  502. const showCheckProjectPopup = async () => {
  503. if (!selectedEquipments.value.length) {
  504. return uni.showToast({ title: '请先选择设备', icon: 'error' })
  505. }
  506. try {
  507. uni.showLoading({ title: '加载中...' })
  508. const firstItem = selectedEquipments.value[0]
  509. const result: any = await requestFunc(TaskOrderFuncName.CheckEquipTaskList, equipType, {
  510. id: firstItem.mainID,
  511. })
  512. if (result?.data) {
  513. equipDataForPopup.value = result.data
  514. }
  515. const projectList: any[][] = []
  516. for (const item of selectedEquipments.value) {
  517. if (item.reportRespVOList || item.reportDOList) {
  518. projectList.push(item.reportRespVOList || item.reportDOList)
  519. }
  520. }
  521. checkProjectList.value = projectList
  522. uni.hideLoading()
  523. } catch (error) {
  524. uni.hideLoading()
  525. console.error('加载设备详情失败:', error)
  526. return uni.showToast({ title: '加载失败', icon: 'error' })
  527. }
  528. selectTemplates.value = {}
  529. currentSelectedCheckProjectItems.value = []
  530. showCheckProject.value = true
  531. }
  532. const closeCheckProjectPopup = () => {
  533. showCheckProject.value = false
  534. currentSelectedCheckProjectItems.value = []
  535. }
  536. const handleCheckProjectChange = (selectedItems: any[]) => {
  537. currentSelectedCheckProjectItems.value = selectedItems
  538. }
  539. const handleCheckProjectConfirm = async (checkProjectItems: any[]) => {
  540. const addProjectList = []
  541. selectedEquipments.value.forEach((item: any) => {
  542. checkProjectItems.forEach((project: any) => {
  543. const newItem = {
  544. ...project,
  545. orderItemId: item.mainID,
  546. }
  547. addProjectList.push(newItem)
  548. })
  549. })
  550. closeCheckProjectPopup()
  551. uni.showLoading({ title: '提交中...' })
  552. try {
  553. await addInspectProject({
  554. itemList: addProjectList,
  555. // 200 表示锅炉 300是管道
  556. type: 200,
  557. })
  558. initSelect()
  559. await fetchCheckerOnlineEquipmentList()
  560. } catch (error) {
  561. console.error('添加检验项目失败:', error)
  562. uni.showToast({ title: '添加项目失败', icon: 'error' })
  563. } finally {
  564. uni.hideLoading()
  565. }
  566. }
  567. const showSuspendPopupFunc = () => {
  568. const networkType = uni.getNetworkType?.()
  569. if (networkType === 'none') {
  570. return uni.showToast({ title: '当前网络连接不可用,请检查网络设置后重新操作', icon: 'error' })
  571. }
  572. if (!selectedEquipments.value.length) {
  573. return uni.showToast({ title: '请先选择设备', icon: 'error' })
  574. }
  575. showMoreOperate.value = false
  576. suspendReasonDict.value = ''
  577. suspendReason.value = ''
  578. showSuspendPopup.value = true
  579. }
  580. const closeSuspendPopup = () => {
  581. showSuspendPopup.value = false
  582. suspendReasonDict.value = ''
  583. suspendReason.value = ''
  584. }
  585. const suspendCheck = async (flag: number) => {
  586. if (!suspendReasonDict.value) {
  587. return uni.showToast({ title: '请选择拒检类别', icon: 'error' })
  588. }
  589. if (suspendReasonDict.value === '5' && !suspendReason.value.trim()) {
  590. return uni.showToast({ title: '请输入拒绝检验原因', icon: 'error' })
  591. }
  592. uni.showLoading({ title: '加载中' })
  593. const ids = selectedEquipments.value.map((item: any) => item.mainID)
  594. try {
  595. const reqData = {
  596. orderItemIds: ids,
  597. reason: suspendReason.value,
  598. reasonDict: suspendReasonDict.value,
  599. flag,
  600. }
  601. const result = await requestFunc(TaskOrderFuncName.BatchSuspendEquip, equipType, reqData)
  602. uni.hideLoading()
  603. if (result?.code === 0) {
  604. initSelect()
  605. fetchCheckerOnlineEquipmentList()
  606. uni.showToast({ title: '中止检验成功', icon: 'success' })
  607. uni.$emit('RefreshOrder')
  608. } else {
  609. const msg = result?.msg || '中止检验失败'
  610. uni.showToast({ title: msg, icon: 'error' })
  611. }
  612. } catch (error) {
  613. uni.showToast({ title: '中止检验失败', icon: 'error' })
  614. } finally {
  615. closeSuspendPopup()
  616. uni.hideLoading()
  617. }
  618. }
  619. const createInform = async () => {
  620. const networkType = uni.getNetworkType?.()
  621. if (networkType === 'none') {
  622. return uni.showToast({ title: '无网络连接,请联网重试' })
  623. }
  624. if (!orderId.value) return
  625. if (selectedEquipments.value.length !== 1) {
  626. return uni.showToast({ title: '只能选择一个设备添加重大问题线索', icon: 'error' })
  627. }
  628. const selectedEquipment = selectedEquipments.value[0]
  629. const majorIssue = selectedEquipment.reportRespVOList.find((item: any) => item.reportType == 500)
  630. if (majorIssue) {
  631. return uni.showToast({ title: '该设备已添加了重大问题线索', icon: 'error' })
  632. }
  633. uni.showLoading({ title: '提交中...', mask: true })
  634. try {
  635. const orderFormResp = await requestFunc(TaskOrderFuncName.GetOrderForm, equipType, {
  636. orderId: orderId.value,
  637. businessType: 400,
  638. orderItemId: selectedEquipment.mainID,
  639. })
  640. if (orderFormResp?.code !== 0) {
  641. return uni.showToast({ title: orderFormResp?.msg || '获取模板失败', icon: 'error' })
  642. }
  643. const templateId = orderFormResp?.data?.templateId || ''
  644. const addMajorIssueResp = await requestFunc(TaskOrderFuncName.AddMajorIssues, equipType, {
  645. orderFormEnterReqVO: {
  646. businessType: 400,
  647. modifiedReason: '',
  648. orderId: orderId.value,
  649. orderItemId: selectedEquipment.mainID,
  650. },
  651. orderId: orderId.value,
  652. orderItemId: selectedEquipment.mainID,
  653. prepareId: userInfo.value?.id || '',
  654. prepareName: userInfo.value?.nickname || '',
  655. templateId: templateId,
  656. })
  657. const refId = addMajorIssueResp?.data || ''
  658. uni.navigateTo({
  659. url: `/pages/editor/mainQuestionEditor?templateId=${templateId}&refId=${refId}`,
  660. })
  661. } catch (error) {
  662. uni.hideLoading()
  663. uni.showToast({ title: '操作失败', icon: 'error' })
  664. }
  665. }
  666. const showAddInspectionplanPopup = async () => {
  667. if (!canAddInspectionplan.value) {
  668. return uni.showToast({ title: '请先选择设备', icon: 'error' })
  669. }
  670. showMoreOperate.value = false
  671. showInspectionplanPopup.value = true
  672. inspectionPlanType.value = 'commPlan'
  673. inspectionplanName.value = ''
  674. selectedTemplate.value = null
  675. try {
  676. const result = await pressure2NotVerifyPageApi({
  677. type: '6',
  678. reportType: 600,
  679. status: 200,
  680. pageNo: 1,
  681. pageSize: 9999,
  682. })
  683. optionsResult.value = result?.data || []
  684. } catch (error) {
  685. console.error('获取模板列表失败:', error)
  686. }
  687. }
  688. const closeInspectionplanPopup = () => {
  689. showInspectionplanPopup.value = false
  690. }
  691. const onTemplateChange = (selected) => {
  692. selectedTemplate.value = selected.selectedItems.label
  693. selectedTemplateId.value = selected.selectedItems.value
  694. }
  695. const addInspectionplanConfirm = async () => {
  696. if (!selectedTemplate.value) {
  697. return uni.showToast({ title: '请选择模板封面', icon: 'error' })
  698. }
  699. if (!inspectionplanName.value.trim()) {
  700. return uni.showToast({ title: '请输入检验方案名称', icon: 'error' })
  701. }
  702. closeInspectionplanPopup()
  703. uni.showLoading({ title: '提交中...', mask: true })
  704. try {
  705. const reqData = {
  706. orderId: orderId.value,
  707. orderItemIds: selectedEquipments.value.map((item) => item.mainID),
  708. prepareId: userInfo.value?.id || '',
  709. prepareJson: JSON.stringify({
  710. prepareName: userInfo.value?.nickname || '',
  711. prepareDate: dayjs().format('YYYY年MM月DD日'),
  712. }),
  713. prepareName: userInfo.value?.nickname || '',
  714. reportName: inspectionplanName.value.trim(),
  715. templateId: selectedTemplateId.value,
  716. }
  717. const res = await requestFunc(TaskOrderFuncName.AddMajorIssues, equipType, reqData)
  718. uni.navigateTo({
  719. url: `/pages/editor/inspectionPlanEditor?templateId=${selectedTemplateId.value}&refId=${res?.data || ''}`,
  720. })
  721. } catch (error) {
  722. uni.hideLoading()
  723. uni.showToast({ title: '操作失败', icon: 'error' })
  724. }
  725. }
  726. const handleUpdateContact = () => {
  727. const current = selectedEquipments.value[0]
  728. currentItem.value = current || {}
  729. showMoreOperate.value = false
  730. showUpdateContactPopup.value = true
  731. }
  732. const handleUpdateSafetyManagerConfirm = async (params: { name: string; phone: string }) => {
  733. if (selectedEquipIds.value.length < 1) {
  734. return uni.showToast({ title: '请选择设备', icon: 'error' })
  735. }
  736. try {
  737. uni.showLoading({ title: '提交中...' })
  738. const selectedEquip = selectedEquipments.value[0]
  739. const results = await updateEquipBoilerSecurityManager({
  740. id: selectedEquip.equipId,
  741. safery: params.name,
  742. saferydh: params.phone,
  743. })
  744. uni.hideLoading()
  745. const isSuccess = results?.code === 0
  746. if (isSuccess) {
  747. uni.showToast({ title: '修改成功', icon: 'success' })
  748. showUpdateContactPopup.value = false
  749. initSelect()
  750. await refreshList()
  751. } else {
  752. uni.showToast({ title: '修改失败', icon: 'none' })
  753. }
  754. } catch (error) {
  755. uni.hideLoading()
  756. console.error('修改安全管理员失败:', error)
  757. uni.showToast({ title: '修改失败', icon: 'none' })
  758. }
  759. }
  760. const handleRouteToEquipmentDetail = (item: any, pageType: string) => {
  761. uni.navigateTo({
  762. url: `/pages/equipment/detail/equipmentDetail?orderId=${orderId.value}&orderItemId=${item.mainID}&equipId=${item.equipId}&pageType=${pageType}&useOnline=1&canEdit=${true}`,
  763. })
  764. }
  765. const handleCalcTotalFee = (reportDOList: any) => {
  766. if (!reportDOList || !Array.isArray(reportDOList)) return 0
  767. return reportDOList
  768. .filter((x: any) => x.fee && !isNaN(x.fee) && typeof x.fee === 'number')
  769. .reduce((sum: number, item: any) => sum + item.fee, 0)
  770. }
  771. </script>
  772. <style lang="scss" scoped>
  773. .equipment-list-container {
  774. display: flex;
  775. flex-direction: column;
  776. height: 100vh;
  777. background-color: #f5f5f5;
  778. }
  779. .navigate-view {
  780. display: flex;
  781. flex-direction: row;
  782. align-items: center;
  783. justify-content: space-between;
  784. height: 44px;
  785. padding: 0 12px;
  786. background-color: #fff;
  787. border-bottom: 1px solid #eee;
  788. }
  789. .navigate-left {
  790. display: flex;
  791. align-items: center;
  792. justify-content: center;
  793. width: 40px;
  794. height: 40px;
  795. }
  796. .back-icon {
  797. width: 20px;
  798. height: 20px;
  799. }
  800. .navigate-title {
  801. flex: 1;
  802. font-size: 17px;
  803. font-weight: 500;
  804. color: #333;
  805. text-align: center;
  806. }
  807. .navigate-right {
  808. width: 40px;
  809. }
  810. .list-scroll {
  811. flex: 1;
  812. overflow: hidden;
  813. }
  814. .equipment-cell {
  815. padding: 12px;
  816. margin: 12px;
  817. margin-bottom: 0;
  818. background-color: #fff;
  819. border-radius: 5px;
  820. }
  821. .cell-top {
  822. padding-bottom: 10px;
  823. margin-bottom: 10px;
  824. border-bottom: 1px solid rgb(244, 244, 244);
  825. }
  826. .row {
  827. display: flex;
  828. flex-direction: row;
  829. align-items: center;
  830. }
  831. .checkbox-wrapper {
  832. margin-right: 10px;
  833. }
  834. .checkbox {
  835. display: flex;
  836. align-items: center;
  837. justify-content: center;
  838. width: 18px;
  839. height: 18px;
  840. border: 1px solid rgb(187, 187, 187);
  841. border-radius: 3px;
  842. }
  843. .checkbox.checked {
  844. background-color: #2f8eff;
  845. border-color: #2f8eff;
  846. }
  847. .check-icon {
  848. width: 12px;
  849. height: 12px;
  850. }
  851. .cell-top-left {
  852. display: flex;
  853. flex: 1;
  854. flex-direction: row;
  855. flex-wrap: wrap;
  856. align-items: center;
  857. justify-content: space-between;
  858. }
  859. .equip-name {
  860. flex: 1;
  861. font-size: 16px;
  862. font-weight: bold;
  863. color: rgb(51, 51, 51);
  864. }
  865. .main-checker {
  866. flex-basis: 160px;
  867. flex-shrink: 0;
  868. font-size: 16px;
  869. font-weight: bold;
  870. color: rgb(51, 51, 51);
  871. }
  872. .status-tag {
  873. display: flex;
  874. flex-basis: 80px;
  875. flex-direction: row;
  876. flex-shrink: 0;
  877. align-items: center;
  878. justify-content: center;
  879. padding: 5px 0;
  880. border-radius: 4px;
  881. }
  882. .status-pending {
  883. background-color: rgba(230, 162, 60, 0.3);
  884. border: 1px solid rgba(230, 162, 60, 0.5);
  885. }
  886. .status-done {
  887. background-color: rgba(103, 194, 58, 0.3);
  888. border: 1px solid rgba(103, 194, 58, 0.5);
  889. }
  890. .status-text {
  891. font-size: 12px;
  892. color: rgb(202, 135, 35);
  893. }
  894. .status-done .status-text {
  895. color: rgb(80, 175, 33);
  896. }
  897. .cell-center {
  898. display: flex;
  899. flex-direction: row;
  900. align-items: center;
  901. justify-content: space-between;
  902. }
  903. .info-box {
  904. display: flex;
  905. flex: 1;
  906. flex-direction: row;
  907. flex-wrap: wrap;
  908. justify-content: space-between;
  909. padding: 8px 12px;
  910. background-color: rgb(244, 244, 244);
  911. border-radius: 5px;
  912. }
  913. .info-label {
  914. margin-top: 8px;
  915. font-size: 12px;
  916. color: rgb(108, 108, 108);
  917. }
  918. .info-value {
  919. color: rgb(51, 51, 51);
  920. }
  921. .cell-bottom {
  922. display: flex;
  923. flex-direction: row;
  924. align-items: center;
  925. justify-content: flex-end;
  926. margin-top: 10px;
  927. }
  928. .claim-btn,
  929. .record-btn {
  930. display: flex;
  931. align-items: center;
  932. justify-content: center;
  933. min-width: 75px;
  934. height: 29px;
  935. padding: 0 5px;
  936. margin-left: 5px;
  937. border-radius: 3px;
  938. }
  939. .blue-btn {
  940. color: white;
  941. background-color: rgb(47, 142, 255);
  942. }
  943. .white-btn {
  944. background-color: #fff;
  945. border: 1px solid rgb(217, 217, 217);
  946. }
  947. .blue-btn-text {
  948. font-size: 12px;
  949. color: rgb(222, 238, 255);
  950. }
  951. .white-btn-text {
  952. font-size: 12px;
  953. color: rgb(59, 59, 59);
  954. }
  955. .loading-text {
  956. padding: 15px;
  957. font-size: 14px;
  958. color: #999;
  959. text-align: center;
  960. }
  961. .empty-text {
  962. display: flex;
  963. flex-direction: column;
  964. align-items: center;
  965. justify-content: center;
  966. padding: 60px 0;
  967. font-size: 14px;
  968. color: #999;
  969. }
  970. .bottom-operate {
  971. display: flex;
  972. flex-direction: row;
  973. align-items: center;
  974. justify-content: space-between;
  975. height: 68px;
  976. padding: 8px 12px;
  977. background-color: #fff;
  978. border-top: 1px solid #eee;
  979. }
  980. .select-all {
  981. display: flex;
  982. flex-direction: row;
  983. align-items: center;
  984. }
  985. .select-all-text {
  986. margin-left: 6px;
  987. font-size: 14px;
  988. color: #333;
  989. }
  990. .btn-group {
  991. display: flex;
  992. flex-direction: row;
  993. align-items: center;
  994. }
  995. .btn-group > button {
  996. min-width: 70px;
  997. height: 32px;
  998. padding: 8px 4px;
  999. margin-left: 6px;
  1000. font-size: 14px;
  1001. line-height: 1;
  1002. border: none;
  1003. border-radius: 3px;
  1004. }
  1005. .operate-btn {
  1006. display: flex;
  1007. align-items: center;
  1008. justify-content: center;
  1009. }
  1010. .popup-mask {
  1011. position: fixed;
  1012. top: 0;
  1013. right: 0;
  1014. bottom: 0;
  1015. left: 0;
  1016. z-index: 999;
  1017. display: flex;
  1018. align-items: center;
  1019. justify-content: center;
  1020. background-color: rgba(0, 0, 0, 0.5);
  1021. }
  1022. .popup-content {
  1023. width: 80%;
  1024. padding: 20px;
  1025. background-color: #fff;
  1026. border-radius: 8px;
  1027. }
  1028. .tips-text {
  1029. display: block;
  1030. margin-bottom: 20px;
  1031. font-size: 16px;
  1032. color: #333;
  1033. text-align: center;
  1034. }
  1035. .popup-actions {
  1036. display: flex;
  1037. flex-direction: row;
  1038. justify-content: space-around;
  1039. }
  1040. .action-btn {
  1041. display: flex;
  1042. align-items: center;
  1043. justify-content: center;
  1044. width: 100px;
  1045. height: 36px;
  1046. font-size: 14px;
  1047. border: none;
  1048. border-radius: 4px;
  1049. }
  1050. .cancel-btn {
  1051. color: #666;
  1052. background-color: #f5f5f5;
  1053. }
  1054. .confirm-btn {
  1055. color: #fff;
  1056. background-color: #2f8eff;
  1057. }
  1058. .warn-btn {
  1059. color: #fff;
  1060. background-color: #e6a23c;
  1061. }
  1062. .suspend-actions {
  1063. gap: 8px;
  1064. }
  1065. .suspend-actions .action-btn {
  1066. flex: 1;
  1067. width: auto;
  1068. }
  1069. .more-operate-overlay {
  1070. position: fixed;
  1071. top: 0;
  1072. right: 0;
  1073. bottom: 0;
  1074. left: 0;
  1075. z-index: 998;
  1076. }
  1077. .more-operate-panel {
  1078. position: fixed;
  1079. right: 0;
  1080. bottom: 68px;
  1081. left: 0;
  1082. z-index: 999;
  1083. display: flex;
  1084. flex-direction: column;
  1085. overflow: hidden;
  1086. background-color: #fff;
  1087. border-radius: 5px;
  1088. box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
  1089. transition: transform 0.3s ease;
  1090. transform: translateY(100%);
  1091. }
  1092. .more-panel-show {
  1093. transform: translateY(0);
  1094. }
  1095. .more-btn-item {
  1096. display: flex;
  1097. align-items: center;
  1098. justify-content: center;
  1099. padding: 0 15px 10px;
  1100. background-color: #fff;
  1101. }
  1102. .more-btn-inner {
  1103. display: flex;
  1104. align-items: center;
  1105. justify-content: center;
  1106. width: 100%;
  1107. padding-top: 10px;
  1108. }
  1109. .more-btn-border {
  1110. border-top: 1px solid #f4f5f6;
  1111. }
  1112. .more-btn-item.disabled {
  1113. opacity: 1;
  1114. }
  1115. .more-btn-text {
  1116. font-size: 14px;
  1117. color: #333;
  1118. }
  1119. .suspend-popup .popup-title,
  1120. .inspectionplan-popup .popup-title {
  1121. display: block;
  1122. margin-bottom: 15px;
  1123. font-size: 16px;
  1124. font-weight: 500;
  1125. color: #333;
  1126. text-align: center;
  1127. }
  1128. .tab-row {
  1129. display: flex;
  1130. margin-bottom: 12px;
  1131. border-radius: 4px;
  1132. overflow: hidden;
  1133. border: 1px solid #2f8eff;
  1134. }
  1135. .tab-item {
  1136. flex: 1;
  1137. padding: 8px 0;
  1138. font-size: 14px;
  1139. color: #2f8eff;
  1140. text-align: center;
  1141. background-color: #fff;
  1142. }
  1143. .tab-item.active {
  1144. color: #fff;
  1145. background-color: #2f8eff;
  1146. }
  1147. .suspend-textarea {
  1148. box-sizing: border-box;
  1149. width: 100%;
  1150. height: 80px;
  1151. padding: 8px;
  1152. margin-bottom: 15px;
  1153. font-size: 14px;
  1154. border: 1px solid #ddd;
  1155. border-radius: 4px;
  1156. }
  1157. .form-item {
  1158. display: flex;
  1159. flex-direction: column;
  1160. margin-bottom: 12px;
  1161. }
  1162. .form-label {
  1163. margin-bottom: 6px;
  1164. font-size: 14px;
  1165. color: #333;
  1166. }
  1167. .picker-value {
  1168. padding: 8px;
  1169. font-size: 14px;
  1170. color: #333;
  1171. background-color: #f5f5f5;
  1172. border-radius: 4px;
  1173. }
  1174. .form-input {
  1175. padding: 0px;
  1176. font-size: 14px;
  1177. background-color: #f5f5f5;
  1178. border-radius: 4px;
  1179. }
  1180. .template-scroll {
  1181. max-height: 200px;
  1182. margin-bottom: 15px;
  1183. }
  1184. .template-item {
  1185. padding: 10px;
  1186. font-size: 14px;
  1187. color: #333;
  1188. border-bottom: 1px solid #eee;
  1189. }
  1190. .template-item.active {
  1191. color: #2f8eff;
  1192. background-color: #f0f8ff;
  1193. }
  1194. .check-project-popup {
  1195. height: 80vh;
  1196. padding: 0;
  1197. overflow: hidden;
  1198. }
  1199. .popup-header {
  1200. display: flex;
  1201. flex-direction: row;
  1202. align-items: center;
  1203. justify-content: space-between;
  1204. padding: 15px;
  1205. border-bottom: 1px solid #eee;
  1206. }
  1207. .popup-close {
  1208. font-size: 20px;
  1209. color: #999;
  1210. }
  1211. </style>