OtherReport.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. <template>
  2. <view class="other-report-section">
  3. <scroll-view class="scroll-view" scroll-y>
  4. <view v-for="group in dataSource" :key="group.reportType" class="group-box">
  5. <view class="group-header">
  6. <text class="group-title">{{ group.reportName }}</text>
  7. </view>
  8. <view
  9. v-for="(item, index) in group.data"
  10. :key="item.id"
  11. class="report-item"
  12. :class="{ 'first-item': index === 0 }"
  13. @click="handleSelectItem(item)"
  14. >
  15. <view class="item-content">
  16. <view class="item-left">
  17. <checkbox
  18. :checked="isSelected(item)"
  19. color="#4B8CD9"
  20. @click.stop="handleSelectItem(item)"
  21. />
  22. <text v-if="showStatusTag(item)" class="status-tag">
  23. {{ getStatusText(item) }}
  24. </text>
  25. <text class="report-name">{{ item.reportName }}</text>
  26. </view>
  27. <view class="item-right">
  28. <view
  29. v-if="canEdit(item)"
  30. class="action-btn edit-btn"
  31. @click.stop="handleEdit(item)"
  32. >
  33. <text>修改</text>
  34. </view>
  35. <view
  36. class="action-btn view-btn"
  37. @click.stop="handleViewDetail(item)"
  38. >
  39. <text>查看详情</text>
  40. </view>
  41. </view>
  42. </view>
  43. </view>
  44. </view>
  45. <view v-if="!dataSource || dataSource.length === 0" class="empty-state">
  46. <text class="empty-text">暂无项目文件</text>
  47. <text class="empty-sub-text">包括重大问题线索告知表、检验方案、操作指导书等</text>
  48. </view>
  49. </scroll-view>
  50. <view class="bottom-bar">
  51. <view class="select-all-box" @click="handleSelectAll">
  52. <checkbox
  53. class="square-checkbox"
  54. :checked="selectAll"
  55. color="#4B8CD9"
  56. @click.stop="handleSelectAll"
  57. />
  58. <text class="select-all-text">{{ selectAll ? '取消全选' : '全选' }}</text>
  59. </view>
  60. <view class="action-buttons">
  61. <view class="operate-btn delete-btn" @click="showDelReportPopup">
  62. <text class="operate-btn-text">作废项目</text>
  63. </view>
  64. </view>
  65. </view>
  66. <TipsPopup ref="tipsPopupRef" />
  67. </view>
  68. </template>
  69. <script lang="ts" setup>
  70. import { ref, watch, withDefaults } from 'vue'
  71. import { PressureReportType, PressureReportTypeMap } from '@/utils/dictMap'
  72. import TipsPopup from './inspectProjectComponent/TipsPopup.vue'
  73. import eventBus from '@/utils/eventBus'
  74. interface ReportItem {
  75. id: string
  76. reportName: string
  77. reportType?: number
  78. status?: number
  79. templateId?: string
  80. equipCode?: string
  81. [key: string]: any
  82. }
  83. interface GroupItem {
  84. reportType: number
  85. reportName: string
  86. data: ReportItem[]
  87. }
  88. interface Props {
  89. otherReportList?: ReportItem[]
  90. useOnline?: string
  91. orderItemId?: string
  92. }
  93. const props = withDefaults(defineProps<Props>(), {
  94. otherReportList: () => [],
  95. })
  96. const emit = defineEmits<{
  97. edit: [item: ReportItem]
  98. viewDetail: [item: ReportItem]
  99. handleEditWorkInstruction: [item: ReportItem]
  100. }>()
  101. const tipsPopupRef = ref<any>(null)
  102. const selectedProjects = ref<ReportItem[]>([])
  103. const selectAll = ref(false)
  104. const itemRefs = ref<Record<string, any>>({})
  105. const dataSource = ref<GroupItem[]>([])
  106. watch(
  107. () => props.otherReportList,
  108. (newList) => {
  109. if (!newList || !Array.isArray(newList)) {
  110. dataSource.value = []
  111. return
  112. }
  113. const array: GroupItem[] = []
  114. for (const item of newList) {
  115. const index = array.findIndex(ele => ele.reportType === item.reportType)
  116. if (index !== -1) {
  117. if (array[index]['data']) {
  118. array[index]['data'].push(item)
  119. } else {
  120. array[index]['data'] = [item]
  121. }
  122. } else {
  123. array.push({
  124. reportType: item.reportType,
  125. reportName: PressureReportTypeMap[item.reportType] || '其他',
  126. data: [item],
  127. })
  128. }
  129. }
  130. dataSource.value = array
  131. },
  132. { immediate: true }
  133. )
  134. const isSelected = (item: ReportItem): boolean => {
  135. return selectedProjects.value.some(p => p.id === item.id)
  136. }
  137. const handleSelectItem = (item: ReportItem) => {
  138. const index = selectedProjects.value.findIndex(p => p.id === item.id)
  139. if (index === -1) {
  140. selectedProjects.value.push(item)
  141. } else {
  142. selectedProjects.value.splice(index, 1)
  143. }
  144. selectAll.value = selectedProjects.value.length === dataSource.value.length
  145. }
  146. const handleSelectAll = () => {
  147. selectAll.value = !selectAll.value
  148. selectedProjects.value = []
  149. if (selectAll.value) {
  150. selectedProjects.value = [...(props.otherReportList || [])]
  151. }
  152. }
  153. const showDelReportPopup = () => {
  154. if (!selectedProjects.value.length) {
  155. uni.showToast({ title: '请选择作废的项目', icon: 'error' })
  156. return
  157. }
  158. const hasMain = selectedProjects.value.some(item => item.reportType === 100)
  159. if (hasMain) {
  160. uni.showToast({ title: '主报告不能作废', icon: 'error' })
  161. return
  162. }
  163. tipsPopupRef.value?.show({
  164. text: '是否作废已选择的检验项目?',
  165. confirm: handleDelReport,
  166. })
  167. }
  168. const handleDelReport = async () => {
  169. try {
  170. uni.showLoading({ title: '作废中...' })
  171. const cancelIds = selectedProjects.value.map(item => item.id)
  172. let result = null
  173. if (props.useOnline === '1') {
  174. const { delReportApi } = await import('@/api/task')
  175. const fetchResult = await delReportApi({ ids: cancelIds })
  176. result = fetchResult.data
  177. } else {
  178. uni.showToast({ title: '离线模式暂不支持作废', icon: 'none' })
  179. uni.hideLoading()
  180. return
  181. }
  182. uni.hideLoading()
  183. if (result) {
  184. eventBus.emit('RefreshOnlineData')
  185. uni.showToast({ title: '作废成功', icon: 'success' })
  186. initSelected()
  187. } else {
  188. uni.showToast({ title: '作废失败', icon: 'error' })
  189. }
  190. } catch (error) {
  191. uni.hideLoading()
  192. console.error('作废失败:', error)
  193. uni.showToast({ title: '作废失败', icon: 'error' })
  194. }
  195. }
  196. const initSelected = () => {
  197. selectAll.value = false
  198. selectedProjects.value = []
  199. for (const key in itemRefs.value) {
  200. if (itemRefs.value[key]?.setSelect) {
  201. itemRefs.value[key].setSelect(false)
  202. }
  203. }
  204. }
  205. const showStatusTag = (item: ReportItem): boolean => {
  206. return [PressureReportType.WORKINSTRUCTION].includes(item.reportType) && item.status !== undefined
  207. }
  208. const getStatusText = (item: ReportItem): string => {
  209. if (item.reportType === PressureReportType.WORKINSTRUCTION) {
  210. switch (item.status) {
  211. case 0:
  212. return '待提交'
  213. case 100:
  214. return '待批准'
  215. case 300:
  216. return '退回'
  217. default:
  218. return ''
  219. }
  220. }
  221. return ''
  222. }
  223. const canEdit = (item: ReportItem): boolean => {
  224. const { reportType, status } = item
  225. if (reportType === PressureReportType.WORKINSTRUCTION) {
  226. return [0, 200, 300].includes(status)
  227. } else if (reportType === PressureReportType.INSPECTIONPLAN) {
  228. return true
  229. }
  230. return false
  231. }
  232. const handleEdit = (item: ReportItem) => {
  233. emit('edit', item)
  234. emit('handleEditWorkInstruction', item)
  235. }
  236. const handleViewDetail = async (item: ReportItem) => {
  237. try {
  238. uni.showLoading({ title: '加载中...' })
  239. const { reportPreviewApi } = await import('@/api')
  240. const params = {
  241. reportId: item?.id,
  242. equipCode: item?.equipCode,
  243. type: 300,
  244. fileType: 200,
  245. isBase64: true,
  246. }
  247. const result = await reportPreviewApi(params)
  248. uni.hideLoading()
  249. if (result) {
  250. const pdfUrl = `data:application/pdf;base64,${result}`
  251. uni.navigateTo({
  252. url: `/pages/preViewPdf/PreViewPdf?title=${encodeURIComponent(item.reportName)}&url=${encodeURIComponent(pdfUrl)}`,
  253. })
  254. }
  255. } catch (error) {
  256. uni.hideLoading()
  257. console.error('获取报告预览失败:', error)
  258. uni.showToast({ title: '获取报告预览失败', icon: 'error' })
  259. }
  260. }
  261. </script>
  262. <style lang="scss" scoped>
  263. .other-report-section {
  264. display: flex;
  265. flex-direction: column;
  266. height: 100%;
  267. background-color: #f5f5f5;
  268. }
  269. .scroll-view {
  270. flex: 1;
  271. padding: 12px;
  272. padding-bottom: 80px;
  273. }
  274. .group-box {
  275. margin-bottom: 12px;
  276. padding: 15px;
  277. background-color: #fff;
  278. border-radius: 5px;
  279. }
  280. .group-header {
  281. padding-bottom: 10px;
  282. margin-bottom: 10px;
  283. border-bottom: 1px solid rgb(244, 244, 244);
  284. }
  285. .group-title {
  286. font-size: 15px;
  287. font-weight: 600;
  288. color: rgb(51, 51, 51);
  289. }
  290. .report-item {
  291. padding: 10px 0;
  292. border-bottom: 1px solid rgb(244, 244, 244);
  293. }
  294. .report-item.last {
  295. border-bottom: none;
  296. }
  297. .first-item {
  298. padding-top: 5px;
  299. }
  300. .item-content {
  301. display: flex;
  302. flex-direction: row;
  303. align-items: center;
  304. justify-content: space-between;
  305. }
  306. .item-left {
  307. display: flex;
  308. flex: 1;
  309. flex-direction: row;
  310. align-items: center;
  311. min-width: 0;
  312. }
  313. .status-tag {
  314. display: flex;
  315. align-items: center;
  316. justify-content: center;
  317. height: 25px;
  318. padding: 0 4px;
  319. margin-right: 5px;
  320. font-size: 12px;
  321. color: rgb(255, 59, 59);
  322. border: 1px solid rgb(255, 59, 59);
  323. border-radius: 3px;
  324. flex-shrink: 0;
  325. }
  326. .report-name {
  327. flex: 1;
  328. min-width: 0;
  329. font-size: 14px;
  330. color: rgb(51, 51, 51);
  331. overflow: hidden;
  332. text-overflow: ellipsis;
  333. white-space: nowrap;
  334. }
  335. .item-right {
  336. display: flex;
  337. flex-direction: row;
  338. align-items: center;
  339. flex-shrink: 0;
  340. margin-left: 10px;
  341. }
  342. .action-btn {
  343. display: flex;
  344. align-items: center;
  345. justify-content: center;
  346. height: 25px;
  347. padding: 0 10px;
  348. border-radius: 3px;
  349. margin-left: 5px;
  350. }
  351. .action-btn text {
  352. font-size: 12px;
  353. color: #fff;
  354. }
  355. .edit-btn {
  356. background-color: rgb(230, 162, 60);
  357. }
  358. .view-btn {
  359. background-color: rgb(47, 142, 255);
  360. }
  361. .empty-state {
  362. display: flex;
  363. flex-direction: column;
  364. align-items: center;
  365. justify-content: center;
  366. padding: 60px 20px;
  367. }
  368. .empty-text {
  369. margin-bottom: 8px;
  370. font-size: 16px;
  371. color: #999;
  372. text-align: center;
  373. }
  374. .empty-sub-text {
  375. font-size: 14px;
  376. color: #ccc;
  377. text-align: center;
  378. }
  379. .bottom-bar {
  380. position: fixed;
  381. right: 0;
  382. bottom: 0;
  383. left: 0;
  384. z-index: 100;
  385. display: flex;
  386. flex-direction: row;
  387. align-items: center;
  388. justify-content: space-between;
  389. height: 68px;
  390. padding: 0 12px;
  391. background-color: #fff;
  392. border-top: 1px solid rgb(244, 244, 244);
  393. }
  394. .select-all-box {
  395. display: flex;
  396. flex-direction: row;
  397. align-items: center;
  398. width: 68px;
  399. }
  400. .square-checkbox {
  401. border-radius: 0 !important;
  402. transform: scale(0.9);
  403. }
  404. .select-all-text {
  405. width: 50px;
  406. margin-left: 10px;
  407. font-size: 15px;
  408. line-height: 1.4;
  409. color: rgb(51, 51, 51);
  410. white-space: pre-line;
  411. }
  412. .action-buttons {
  413. display: flex;
  414. flex-direction: row;
  415. align-items: center;
  416. }
  417. .operate-btn {
  418. display: flex;
  419. align-items: center;
  420. justify-content: center;
  421. min-width: 66px;
  422. height: 40px;
  423. padding: 0 5px;
  424. margin-left: 5px;
  425. border-radius: 4px;
  426. }
  427. .operate-btn-text {
  428. font-size: 14px;
  429. color: rgb(251, 253, 255);
  430. }
  431. .delete-btn {
  432. background-color: #ff4445;
  433. }
  434. </style>