Просмотр исходного кода

Merge branch 'stable' of http://39.98.153.250:9080/yudao/yudao-admin-yiqun into stable

徐展城 2 недель назад
Родитель
Сommit
455545a2c7

+ 36 - 0
yudao-ui-admin-vue3/src/api/pressure2/taskOrderFile/index.ts

@@ -0,0 +1,36 @@
+import request from '@/config/axios'
+import { buildFileUrl } from '@/utils'
+
+/** 任务单附件 API */
+export const TaskOrderFileApi = {
+  /**
+   * 上传文件并创建附件记录
+   * @param formData - 包含 file, orderId, orderItemId, equipMainType
+   */
+  uploadAndCreate: (formData: FormData) =>
+    request.post({
+      url: '/pressure2/task-order-file/upload-and-create',
+      data: formData,
+      headers: { 'Content-Type': 'multipart/form-data' }
+    }),
+
+  /**
+   * 分页查询附件列表
+   */
+  getPage: (params: Recordable) =>
+    request.get({ url: '/pressure2/task-order-file/page', params }),
+
+  /**
+   * 删除附件
+   */
+  deleteFile: (id: string) =>
+    request.delete({ url: '/pressure2/task-order-file/delete', params: { id } }),
+
+  /**
+   * 下载附件
+   */
+  downloadFile: (fileUrl: string, fileName: string) => {
+    const url = buildFileUrl(fileUrl)
+    return request.download({ url })
+  }
+}

+ 15 - 0
yudao-ui-admin-vue3/src/views/pressure2/boilerchecker/components/StatusOperationPanel.vue

@@ -597,6 +597,15 @@
       @confirm="handleRefresh"
     />
 
+    <!-- 上传附件弹窗 -->
+    <el-dialog v-model="attachmentDialogVisible" title="上传附件" width="800px" :close-on-click-modal="false">
+      <AttachmentUpload
+        :order-id="props.taskInfo?.id || ''"
+        :order-item-id="props.taskOrderItem?.id || ''"
+        :equip-main-type="200"
+      />
+    </el-dialog>
+
     <!-- 回退原因弹窗(面板收缩时使用) -->
     <el-dialog
       v-model="rejectionReasonDialogVisible"
@@ -706,6 +715,10 @@
           :disabled="checkerIsLoginUser"
           size="small"
         >{{ selectedItem.reportType === PressureReportType['SUGGUESTION'] ? '编制意见书' : '填写记录' }}</el-button>
+        <!-- 上传附件 -->
+        <el-button type="primary" plain size="small" @click="attachmentDialogVisible = true">
+          上传附件
+        </el-button>
         <!-- 撤销流程:报告审核或审批阶段,有OA流程ID且当前用户是发起人时显示 -->
         <el-button
           v-if="isCanCancelFlow"
@@ -907,6 +920,7 @@ import { is } from '@/utils/is'
 import _ from 'lodash'
 import { uploadFile } from '@/api/common/index'
 import ReportListUploadModal from './reportListUploadModal.vue'
+import AttachmentUpload from '@/views/pressure2/components/AttachmentUpload.vue'
 import AssociationOperationManual from './AssociationOperationManual.vue'
 import {DynamicTbInsApi} from "@/api/pressure2/dynamictbins";
 import {SpreadViewer} from "@/components/DynamicReport";
@@ -2341,6 +2355,7 @@ const handleShowVersionInfo = (item) => {
 
 
 // 检验项目添加上传附件功能
+const attachmentDialogVisible = ref(false)
 const reportListUploadModalRef = ref<InstanceType<typeof ReportListUploadModal>>()
 const handleUploadItem = () => {
   reportListUploadModalRef.value?.openModal(route.query.type === 'BoilerMyTask')

+ 240 - 0
yudao-ui-admin-vue3/src/views/pressure2/components/AttachmentUpload.vue

@@ -0,0 +1,240 @@
+<template>
+  <div class="attachment-upload">
+    <!-- 上传按钮 -->
+    <el-upload
+      action="#"
+      :file-list="uploadFileList"
+      :before-upload="beforeUpload"
+      :http-request="handleUpload"
+      :show-file-list="false"
+      :multiple="true"
+      :disabled="!canUpload"
+    >
+      <el-button type="primary" :disabled="!canUpload">
+        <el-icon><UploadFilled /></el-icon>
+        添加附件
+      </el-button>
+    </el-upload>
+
+    <!-- 已上传附件列表 -->
+    <div v-if="localFileList.length > 0" class="attachment-list">
+      <div v-for="file in localFileList" :key="file.id" class="attachment-item">
+        <div class="attachment-row-top">
+          <el-icon :size="18" class="file-icon"><Document /></el-icon>
+          <span class="file-name">{{ file.fileName }}</span>
+        </div>
+        <div class="attachment-row-bottom">
+          <span class="upload-time" v-if="file.createTime">{{ formatDate(file.createTime) }}</span>
+          <div class="attachment-actions">
+            <el-button type="primary" link size="small" @click="handlePreview(file)">
+              <el-icon><View /></el-icon>
+              预览
+            </el-button>
+            <el-button type="primary" link size="small" @click="handleDownload(file)">
+              <el-icon><Download /></el-icon>
+              下载
+            </el-button>
+            <el-button type="danger" link size="small" @click="handleDelete(file)">
+              <el-icon><Delete /></el-icon>
+              删除
+            </el-button>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import { UploadFilled, Document, View, Download, Delete } from '@element-plus/icons-vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { TaskOrderFileApi } from '@/api/pressure2/taskOrderFile'
+import { buildFileUrl } from '@/utils'
+import { formatDate } from '@/utils/formatTime'
+import request from '@/config/axios'
+
+const props = defineProps({
+  /** 任务单ID */
+  orderId: {
+    type: String,
+    default: ''
+  },
+  /** 检验设备单ID */
+  orderItemId: {
+    type: String,
+    default: ''
+  },
+  /** 设备类型 200锅炉 300管道 */
+  equipMainType: {
+    type: Number,
+    default: 0
+  },
+  /** 是否可上传 */
+  canUpload: {
+    type: Boolean,
+    default: true
+  }
+})
+
+// 本地文件列表
+const localFileList = ref<Recordable[]>([])
+// 上传组件显示用的空列表
+const uploadFileList = ref<Recordable[]>([])
+
+// 页面加载时自动获取附件列表
+onMounted(() => {
+  fetchFileList()
+})
+
+const beforeUpload = (file: File) => {
+  const maxSize = 50 * 1024 * 1024
+  if (file.size > maxSize) {
+    ElMessage.error('文件大小不能超过50MB')
+    return false
+  }
+  return true
+}
+
+// 处理文件上传
+const handleUpload = async (options: Recordable) => {
+  const { file } = options
+  const formData = new FormData()
+  formData.append('file', file)
+  formData.append('orderId', props.orderId || '')
+  formData.append('orderItemId', props.orderItemId || '')
+  formData.append('equipMainType', String(props.equipMainType))
+
+  try {
+    const res = await TaskOrderFileApi.uploadAndCreate(formData)
+    if (res) {
+      ElMessage.success('文件上传成功')
+      fetchFileList()
+    }
+  } catch (error: any) {
+    ElMessage.error(error?.message || '文件上传失败')
+  }
+}
+
+// 预览附件
+const handlePreview = (file: Recordable) => {
+  const url = buildFileUrl(file.fileUrl)
+  window.open(url, '_blank')
+}
+
+// 下载附件 - 使用 blob 方式确保触发下载而不是浏览器打开
+const handleDownload = async (file: Recordable) => {
+  try {
+    const url = buildFileUrl(file.fileUrl)
+    const blob = await request.download({ url })
+    const blobUrl = URL.createObjectURL(blob as unknown as Blob)
+    const link = document.createElement('a')
+    link.href = blobUrl
+    link.download = file.fileName || '下载文件'
+    document.body.appendChild(link)
+    link.click()
+    document.body.removeChild(link)
+    URL.revokeObjectURL(blobUrl)
+  } catch (error: any) {
+    ElMessage.error('下载失败')
+  }
+}
+
+// 删除附件
+const handleDelete = async (file: Recordable) => {
+  try {
+    await ElMessageBox.confirm('确认删除该附件?', '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    })
+    await TaskOrderFileApi.deleteFile(file.id)
+    ElMessage.success('删除成功')
+    fetchFileList()
+  } catch (error: any) {
+    if (error !== 'cancel') {
+      ElMessage.error(error?.message || '删除失败')
+    }
+  }
+}
+
+// 刷新附件列表
+const fetchFileList = async () => {
+  if (!props.orderItemId) return
+  try {
+    const res = await TaskOrderFileApi.getPage({
+      orderItemId: props.orderItemId,
+      pageNo: 1,
+      pageSize: 100
+    })
+    if (res?.list) {
+      localFileList.value = res.list
+    }
+  } catch (error: any) {
+    console.error('获取附件列表失败:', error)
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.attachment-upload {
+  .attachment-list {
+    margin-top: 12px;
+
+    .attachment-item {
+      display: flex;
+      flex-direction: column;
+      padding: 10px 12px;
+      margin-bottom: 8px;
+      border: 1px solid var(--el-border-color-light);
+      border-radius: 4px;
+      background-color: var(--el-fill-color-lighter);
+      transition: background-color 0.2s;
+
+      &:hover {
+        background-color: var(--el-fill-color-light);
+      }
+
+      .attachment-row-top {
+        display: flex;
+        align-items: flex-start;
+        gap: 8px;
+        margin-bottom: 6px;
+
+        .file-icon {
+          flex-shrink: 0;
+          margin-top: 2px;
+          color: var(--el-color-primary);
+        }
+
+        .file-name {
+          flex: 1;
+          font-size: 13px;
+          line-height: 1.5;
+          word-break: break-all;
+          color: var(--el-text-color-primary);
+        }
+      }
+
+      .attachment-row-bottom {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding-left: 26px;
+
+        .upload-time {
+          font-size: 12px;
+          color: var(--el-text-color-secondary);
+        }
+
+        .attachment-actions {
+          display: flex;
+          align-items: center;
+          gap: 4px;
+          margin-left: auto;
+        }
+      }
+    }
+  }
+}
+</style>

+ 15 - 0
yudao-ui-admin-vue3/src/views/pressure2/pipechecker/components/StatusOperationPanel.vue

@@ -596,6 +596,15 @@
       @confirm="handleRefresh"
     />
 
+    <!-- 上传附件弹窗 -->
+    <el-dialog v-model="attachmentDialogVisible" title="上传附件" width="800px" :close-on-click-modal="false">
+      <AttachmentUpload
+        :order-id="props.taskInfo?.id || ''"
+        :order-item-id="props.taskOrderItem?.id || ''"
+        :equip-main-type="300"
+      />
+    </el-dialog>
+
     <!-- 回退原因弹窗(面板收缩时使用) -->
     <el-dialog
       v-model="rejectionReasonDialogVisible"
@@ -705,6 +714,10 @@
           :disabled="checkerIsLoginUser"
           size="small"
         >{{ selectedItem.reportType === PressureReportType['SUGGUESTION'] ? '编制意见书' : '填写记录' }}</el-button>
+        <!-- 上传附件 -->
+        <el-button type="primary" plain size="small" @click="attachmentDialogVisible = true">
+          上传附件
+        </el-button>
         <!-- 撤销流程:报告审核或审批阶段,有OA流程ID且当前用户是发起人时显示 -->
         <el-button
           v-if="isCanCancelFlow"
@@ -899,6 +912,7 @@ import {is} from '@/utils/is'
 import _ from 'lodash'
 import {uploadFile} from '@/api/common/index'
 import ReportListUploadModal from './reportListUploadModal.vue'
+import AttachmentUpload from '@/views/pressure2/components/AttachmentUpload.vue'
 import AssociationOperationManual from './AssociationOperationManual.vue'
 import {SpreadViewer} from "@/components/DynamicReport";
 import {InitParams} from "@/components/DynamicReport/SpreadInterface";
@@ -2223,6 +2237,7 @@ const handleShowVersionInfo = (item) => {
 
 
 // 检验项目添加上传附件功能
+const attachmentDialogVisible = ref(false)
 const reportListUploadModalRef = ref<InstanceType<typeof ReportListUploadModal>>()
 const handleUploadItem = () => {
   reportListUploadModalRef.value?.openModal(route.query.type === 'PipeMyTask')

+ 103 - 0
yudao-ui-admin-vue3/src/views/pressure2/reportArchivingBoiler/detail.vue

@@ -347,6 +347,60 @@ const detailTableSchema = reactive<TableItem[]>([
       }
     ],
     dataField: 'itemFiles'
+  },
+  {
+    title: '上传附件',
+    columns: [
+      {
+        label: '序号',
+        fieldProps: {
+          type: 'index',
+          align: 'center',
+          width: 55
+        }
+      },
+      {
+        label: '文件名称',
+        prop: 'fileName',
+        fieldProps: {
+          align: 'center',
+          minWidth: 200,
+          showOverflowTooltip: true
+        }
+      },
+      {
+        label: '上传时间',
+        prop: 'createTime',
+        fieldProps: {
+          align: 'center',
+          minWidth: 160
+        },
+        render: (_row, value) => {
+          return formatDate(value, 'YYYY-MM-DD HH:mm:ss') || '-'
+        }
+      },
+      {
+        label: '操作',
+        prop: 'operation',
+        fieldProps: {
+          align: 'center',
+          minWidth: 200
+        },
+        render: (row) => {
+          return (
+            <>
+              <el-button type="primary" link onClick={() => handleAttachmentPreview(row)}>
+                预览
+              </el-button>
+              <el-button type="primary" link onClick={() => handleAttachmentDownload(row)}>
+                下载
+              </el-button>
+            </>
+          )
+        }
+      }
+    ],
+    dataField: 'attachmentFiles'
   }
 ])
 
@@ -815,6 +869,55 @@ const handlePreviewOrder = (row) => {
 
 const showAssociationOperationManual = ref(false)
 
+// 附件预览
+const handleAttachmentPreview = (row: Recordable) => {
+  const url = buildFileUrl(row.fileUrl)
+  const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg']
+  const ext = row.fileName?.substring(row.fileName.lastIndexOf('.')).toLowerCase()
+  if (imageExts.includes(ext)) {
+    docPdfUrls.value = [url]
+    showDocPdfDialog.value = true
+  } else {
+    window.open(url, '_blank')
+  }
+}
+
+// 附件下载
+const handleAttachmentDownload = async (row: Recordable) => {
+  try {
+    const url = buildFileUrl(row.fileUrl)
+    const blob = await request.download({ url })
+    const blobUrl = URL.createObjectURL(blob as unknown as Blob)
+    const link = document.createElement('a')
+    link.href = blobUrl
+    link.download = row.fileName || '下载文件'
+    document.body.appendChild(link)
+    link.click()
+    document.body.removeChild(link)
+    URL.revokeObjectURL(blobUrl)
+  } catch (error: any) {
+    ElMessage.error('下载失败')
+  }
+}
+
+// 附件删除
+const handleAttachmentDelete = async (row: Recordable) => {
+  try {
+    await ElMessageBox.confirm('确认删除该附件?', '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    })
+    await request.delete({ url: '/pressure2/task-order-file/delete', params: { id: row.id } })
+    ElMessage.success('删除成功')
+    getDetail()
+  } catch (error: any) {
+    if (error !== 'cancel') {
+      ElMessage.error(error?.message || '删除失败')
+    }
+  }
+}
+
 onMounted(() => {
   getDetail()
 })

+ 1 - 0
yudao-ui-admin-vue3/src/views/pressure2/reportArchivingBoiler/index.api.ts

@@ -16,4 +16,5 @@ export interface DetailData {
   inspectionItems: Recordable[]
   itemFiles: Recordable[]
   taskListFiles: Recordable[]
+  attachmentFiles: Recordable[]
 }

+ 103 - 0
yudao-ui-admin-vue3/src/views/pressure2/reportArchivingPipe/detail.vue

@@ -347,6 +347,60 @@ const detailTableSchema = reactive<TableItem[]>([
       }
     ],
     dataField: 'itemFiles'
+  },
+  {
+    title: '上传附件',
+    columns: [
+      {
+        label: '序号',
+        fieldProps: {
+          type: 'index',
+          align: 'center',
+          width: 55
+        }
+      },
+      {
+        label: '文件名称',
+        prop: 'fileName',
+        fieldProps: {
+          align: 'center',
+          minWidth: 200,
+          showOverflowTooltip: true
+        }
+      },
+      {
+        label: '上传时间',
+        prop: 'createTime',
+        fieldProps: {
+          align: 'center',
+          minWidth: 160
+        },
+        render: (_row, value) => {
+          return formatDate(value, 'YYYY-MM-DD HH:mm:ss') || '-'
+        }
+      },
+      {
+        label: '操作',
+        prop: 'operation',
+        fieldProps: {
+          align: 'center',
+          minWidth: 200
+        },
+        render: (row) => {
+          return (
+            <>
+              <el-button type="primary" link onClick={() => handleAttachmentPreview(row)}>
+                预览
+              </el-button>
+              <el-button type="primary" link onClick={() => handleAttachmentDownload(row)}>
+                下载
+              </el-button>
+            </>
+          )
+        }
+      }
+    ],
+    dataField: 'attachmentFiles'
   }
 ])
 
@@ -816,6 +870,55 @@ const handlePreviewOrder = (row) => {
 
 const showAssociationOperationManual = ref(false)
 
+// 附件预览
+const handleAttachmentPreview = (row: Recordable) => {
+  const url = buildFileUrl(row.fileUrl)
+  const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg']
+  const ext = row.fileName?.substring(row.fileName.lastIndexOf('.')).toLowerCase()
+  if (imageExts.includes(ext)) {
+    docPdfUrls.value = [url]
+    showDocPdfDialog.value = true
+  } else {
+    window.open(url, '_blank')
+  }
+}
+
+// 附件下载
+const handleAttachmentDownload = async (row: Recordable) => {
+  try {
+    const url = buildFileUrl(row.fileUrl)
+    const blob = await request.download({ url })
+    const blobUrl = URL.createObjectURL(blob as unknown as Blob)
+    const link = document.createElement('a')
+    link.href = blobUrl
+    link.download = row.fileName || '下载文件'
+    document.body.appendChild(link)
+    link.click()
+    document.body.removeChild(link)
+    URL.revokeObjectURL(blobUrl)
+  } catch (error: any) {
+    ElMessage.error('下载失败')
+  }
+}
+
+// 附件删除
+const handleAttachmentDelete = async (row: Recordable) => {
+  try {
+    await ElMessageBox.confirm('确认删除该附件?', '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    })
+    await request.delete({ url: '/pressure2/task-order-file/delete', params: { id: row.id } })
+    ElMessage.success('删除成功')
+    getDetail()
+  } catch (error: any) {
+    if (error !== 'cancel') {
+      ElMessage.error(error?.message || '删除失败')
+    }
+  }
+}
+
 onMounted(() => {
   getDetail()
 })

+ 1 - 0
yudao-ui-admin-vue3/src/views/pressure2/reportArchivingPipe/index.api.ts

@@ -16,4 +16,5 @@ export interface DetailData {
   inspectionItems: Recordable[]
   itemFiles: Recordable[]
   taskListFiles: Recordable[]
+  attachmentFiles: Recordable[]
 }