Преглед на файлове

feat: 支持保养工单附件上传保存

- 新增保养工单附件上传、预览和删除展示
- 详情页增加附件保存按钮,仅提交 id 和 attachments
- 新增 hiWorkOrderAttachment 附件保存接口封装
- 隐藏上传组件内部列表占位,避免上传后显示空白卡片
Zimo преди 2 дни
родител
ревизия
94d743f275

+ 5 - 0
src/api/pms/iotmainworkorder/index.ts

@@ -83,6 +83,11 @@ export const IotMainWorkOrderApi = {
     return await request.put({ url: `/pms/iot-main-work-order/fillWorkOrder`, data })
   },
 
+  // 保存保养工单附件
+  hiWorkOrderAttachment: async (data: Pick<IotMainWorkOrderVO, 'id'> & { attachments: any[] }) => {
+    return await request.put({ url: `/pms/iot-main-work-order/hiWorkOrderAttachment`, data })
+  },
+
   // 修改保养工单
   modifyWorkOrder: async (data: any) => {
     return await request.put({ url: `/pms/iot-main-work-order/modifyWorkOrder`, data })

+ 171 - 2
src/views/pms/iotmainworkorder/IotMainWorkOrderAdd.vue

@@ -70,6 +70,37 @@
                 :placeholder="t('faultForm.rHolder')" />
             </el-form-item>
           </el-col>
+          <el-col :span="8">
+            <el-form-item label="附件">
+              <FileUpload
+                :device-id="undefined"
+                :show-folder-button="false"
+                @upload-success="handleUploadSuccess" />
+
+              <div
+                v-if="formData.attachments && formData.attachments.length > 0"
+                class="attachment-container">
+                <div class="attachment-list">
+                  <div
+                    v-for="(attachment, index) in formData.attachments"
+                    :key="attachment.id || index"
+                    class="attachment-item">
+                    <a class="attachment-name" @click="inContent(attachment)">
+                      {{ attachment.filename }}
+                    </a>
+                    <el-button type="danger" link size="small" @click="removeAttachment(index)">
+                      删除
+                    </el-button>
+                  </div>
+                </div>
+              </div>
+              <div
+                v-else-if="!formData.attachments || formData.attachments.length === 0"
+                class="no-attachment">
+                无附件
+              </div>
+            </el-form-item>
+          </el-col>
         </el-row>
       </div>
     </el-form>
@@ -382,6 +413,8 @@ import MaterialListDrawer from '@/views/pms/iotmainworkorder/SelectedMaterialDra
 import WorkOrderMaterial from '@/views/pms/iotmainworkorder/WorkOrderMaterial.vue'
 import { IotDevicePersonApi } from '@/api/pms/iotdeviceperson'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import FileUpload from '@/components/UploadFile/src/FileUpload.vue'
+import { Base64 } from 'js-base64'
 
 /** 保养计划 表单 */
 defineOptions({ name: 'IotMainWorkOrderAdd' })
@@ -421,9 +454,104 @@ const formData = ref({
   outsourcingFlag: 0,
   remark: undefined,
   status: undefined,
-  devicePersons: ''
+  devicePersons: '',
+  attachments: [] as any[]
 })
 
+const getFileType = (filename: string) => {
+  const ext = filename.split('.').pop()?.toLowerCase()
+  if (['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(ext || '')) {
+    return 'image'
+  } else if (['pdf'].includes(ext || '')) {
+    return 'pdf'
+  } else if (['doc', 'docx'].includes(ext || '')) {
+    return 'word'
+  } else if (['xls', 'xlsx'].includes(ext || '')) {
+    return 'excel'
+  }
+  return 'other'
+}
+
+const formatFileSize = (bytes: number) => {
+  if (bytes === 0) return '0 Bytes'
+  const k = 1024
+  const sizes = ['Bytes', 'KB', 'MB', 'GB']
+  const i = Math.floor(Math.log(bytes) / Math.log(k))
+  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
+}
+
+const handleUploadSuccess = (result: any) => {
+  try {
+    if (!result.response) {
+      message.error('上传响应数据异常')
+      return
+    }
+
+    if (result.response.code !== 0) {
+      message.error(result.response.msg || '文件上传失败')
+      return
+    }
+
+    const responseData = result.response.data
+
+    if (!responseData) {
+      message.error('上传数据为空')
+      return
+    }
+
+    if (responseData.files && Array.isArray(responseData.files) && responseData.files.length > 0) {
+      responseData.files.forEach((file: any) => {
+        if (!file.filePath) {
+          return
+        }
+
+        const attachment = {
+          id: undefined,
+          category: 'main_work_order',
+          bizId: formData.value.id,
+          type: 'attachment',
+          filename: file.name || '未知文件',
+          fileType: getFileType(file.name),
+          filePath: file.filePath,
+          fileSize: formatFileSize(file.size || 0),
+          remark: ''
+        }
+
+        if (!formData.value.attachments) {
+          formData.value.attachments = []
+        }
+        formData.value.attachments.push(attachment)
+      })
+
+      message.success(`成功上传 ${responseData.files.length} 个文件`)
+    } else {
+      message.warning('上传成功但未获取到文件信息')
+    }
+  } catch (error) {
+    message.error('处理上传结果失败')
+  }
+}
+
+const removeAttachment = (index: number) => {
+  if (formData.value.attachments && formData.value.attachments.length > index) {
+    formData.value.attachments.splice(index, 1)
+  }
+}
+
+const inContent = async (attachment: any) => {
+  if (!attachment || !attachment.filePath) {
+    message.error('附件路径不存在')
+    return
+  }
+
+  try {
+    const encodedPath = encodeURIComponent(Base64.encode(attachment.filePath))
+    window.open(`http://doc.deepoil.cc:8012/onlinePreview?url=${encodedPath}`)
+  } catch (error) {
+    message.error('预览附件失败')
+  }
+}
+
 /** 获取当前所有设备ID集合 */
 const getCurrentDeviceIds = (): number[] => {
   return [...new Set(list.value.map((item) => item.deviceId))]
@@ -1003,7 +1131,8 @@ const resetForm = () => {
     deviceName: undefined,
     processInstanceId: undefined,
     auditStatus: undefined,
-    deptId: undefined
+    deptId: undefined,
+    attachments: []
   }
   formRef.value?.resetFields()
 }
@@ -1087,4 +1216,44 @@ const handleDelete = async (str: string) => {
   color: #606266;
   background: white;
 }
+
+.attachment-container {
+  width: 100%;
+}
+
+.attachment-list {
+  width: 100%;
+  padding: 10px;
+  margin-top: 5px;
+  background-color: #fafafa;
+  border: 1px solid #e0e0e0;
+  border-radius: 4px;
+  box-sizing: border-box;
+}
+
+.attachment-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 8px 12px;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.attachment-item:last-child {
+  border-bottom: none;
+}
+
+.attachment-name {
+  flex: 1;
+  font-size: 12px;
+  color: #606266;
+  cursor: pointer;
+}
+
+.no-attachment {
+  padding: 10px;
+  margin-top: 5px;
+  font-style: italic;
+  color: #909399;
+}
 </style>

Файловите разлики са ограничени, защото са твърде много
+ 360 - 192
src/views/pms/iotmainworkorder/IotMainWorkOrderDetail.vue


Файловите разлики са ограничени, защото са твърде много
+ 222 - 182
src/views/pms/iotmainworkorder/IotMainWorkOrderOptimize.vue


Някои файлове не бяха показани, защото твърде много файлове са промени