Explorar el Código

feat(special-equipment): 新增特种设备铭牌照片上传(必填)

- types.ts: RiskSpecialEquipmentBo/Vo 加 nameplateUrl + nameplateName 字段
- specialEquipment/index.vue:
  - 新增/编辑弹窗新增'设备铭牌照片'必填上传框(单张,JPG/PNG,5MB)
  - 上传走 axios baseURL,直接调 uploadFile(MinIO)
  - 编辑模式自动回填 nameplatePhotoPreview + form.nameplateUrl
  - reset 时清空 nameplatePhotoPreview + nameplateUrl/Name
  - rules 加 nameplateUrl 必填校验器
  - 修复 DictDataVO.status 历史类型错误

后端字段: org.dromara.risk.domain.RiskSpecialEquipment.nameplateUrl/Name
yangjingjing hace 2 días
padre
commit
af0cf1a993
Se han modificado 2 ficheros con 107 adiciones y 4 borrados
  1. 4 0
      src/api/risk/types.ts
  2. 103 4
      src/views/risk/specialEquipment/index.vue

+ 4 - 0
src/api/risk/types.ts

@@ -432,6 +432,8 @@ export interface RiskSpecialEquipmentBo {
   storeName?: string;
   location?: string;
   photoList?: string | string[];
+  nameplateUrl?: string;
+  nameplateName?: string;
   installDate?: string;
   useStatus?: string;
   inspectionStatus?: string;
@@ -456,6 +458,8 @@ export interface RiskSpecialEquipmentVo {
   storeName: string;
   location: string;
   photoList: string | string[];
+  nameplateUrl: string;
+  nameplateName: string;
   installDate: string;
   useStatus: string;
   inspectionStatus: string;

+ 103 - 4
src/views/risk/specialEquipment/index.vue

@@ -153,6 +153,29 @@
           </el-col>
         </el-row>
 
+        <!-- 设备铭牌照片(必填,单张) -->
+        <el-form-item label="设备铭牌照片" prop="nameplateUrl">
+          <div class="upload-row">
+            <el-button type="primary" plain @click="triggerNameplateUpload">选择文件</el-button>
+            <span class="upload-tip">{{ nameplatePhotoPreview ? '已选择 1 个文件' : '未选择任何文件' }}</span>
+            <el-button v-if="nameplatePhotoPreview" link type="danger" style="margin-left: 8px" @click="clearNameplatePhoto">清空</el-button>
+          </div>
+          <input
+            ref="nameplatePhotoInputRef"
+            type="file"
+            accept=".jpg,.jpeg,.png"
+            style="display: none"
+            @change="onNameplatePhotoInputChange"
+          />
+          <div v-if="nameplatePhotoPreview" class="photo-preview-list">
+            <div class="photo-thumb">
+              <el-image :src="nameplatePhotoPreview" :preview-src-list="[nameplatePhotoPreview]" :initial-index="0" fit="cover" />
+              <el-icon class="photo-remove" @click="removeNameplatePhoto"><CircleCloseFilled /></el-icon>
+            </div>
+          </div>
+          <div class="el-upload__tip">支持 JPG、PNG 格式,大小不超过 5MB(必填)</div>
+        </el-form-item>
+
         <el-form-item label="设备照片">
           <div class="upload-row">
             <el-button type="primary" plain @click="triggerEquipmentUpload">选择文件</el-button>
@@ -419,6 +442,10 @@ const equipmentPhotoList = ref<any[]>([]);
 const equipmentPhotoPreview = ref<string[]>([]);
 const equipmentPhotoInputRef = ref<HTMLInputElement>();
 
+// 设备铭牌照片(必填,单张)
+const nameplatePhotoPreview = ref<string>('');
+const nameplatePhotoInputRef = ref<HTMLInputElement>();
+
 // 维护照片队列(自管文件)
 const maintainPreviewPhotos = ref<string[]>([]);
 const maintainPhotoInputRef = ref<HTMLInputElement>();
@@ -434,7 +461,9 @@ const data = reactive<any>({
     equipmentName: '',
     equipmentType: '',
     location: '',
-    photoList: []
+    photoList: [],
+    nameplateUrl: '',
+    nameplateName: ''
   } as any,
   queryParams: {
     pageNum: 1,
@@ -449,7 +478,20 @@ const data = reactive<any>({
     equipmentCode: [{ required: true, message: '设备编号不能为空', trigger: 'blur' }],
     equipmentName: [{ required: true, message: '设备名称不能为空', trigger: 'blur' }],
     equipmentType: [{ required: true, message: '请选择设备类型', trigger: 'change' }],
-    location: [{ required: true, message: '请输入安装位置', trigger: 'blur' }]
+    location: [{ required: true, message: '请输入安装位置', trigger: 'blur' }],
+    // 设备铭牌照片:必填(新增校验,失败时弹错)
+    nameplateUrl: [
+      {
+        validator: (_rule: any, value: any, callback: any) => {
+          if (!value) {
+            callback(new Error('请上传设备铭牌照片'));
+          } else {
+            callback();
+          }
+        },
+        trigger: 'change'
+      }
+    ]
   }
 });
 
@@ -534,7 +576,7 @@ const recordEquipmentName = ref('');
 
 const mapDictData = (list: DictDataVO[]) => {
   return (list || [])
-    .filter((d) => d.status === '0' || d.status === undefined)
+    .filter((d: any) => d.status === '0' || d.status === undefined)
     .sort((a, b) => (a.dictSort || 0) - (b.dictSort || 0))
     .map((d) => ({ label: d.dictLabel, value: d.dictValue }));
 };
@@ -639,6 +681,8 @@ const handleUpdate = async (row: RiskSpecialEquipmentVo) => {
   const photos = parsePhotoList(form.value.photoList);
   equipmentPhotoPreview.value = [...photos];
   equipmentPhotoList.value = [];
+  // 回填设备铭牌照片(编辑场景)
+  nameplatePhotoPreview.value = form.value.nameplateUrl || '';
   dialog.visible = true;
   dialog.title = '编辑特种设备';
 };
@@ -701,6 +745,58 @@ const clearEquipmentPhotos = () => {
   equipmentPhotoPreview.value = [];
 };
 
+// ============ 设备铭牌照片上传(必填,单张) ============
+const triggerNameplateUpload = () => {
+  nameplatePhotoInputRef.value?.click();
+};
+
+const onNameplatePhotoInputChange = async (e: Event) => {
+  const input = e.target as HTMLInputElement;
+  const files = Array.from(input.files || []);
+  if (files.length === 0) return;
+  const file = files[0];
+  if (!['image/jpeg', 'image/png', 'image/jpg'].includes(file.type)) {
+    ElMessage.error(`文件 ${file.name} 格式不支持,仅允许 JPG/PNG`);
+    input.value = '';
+    return;
+  }
+  if (file.size / 1024 / 1024 >= 5) {
+    ElMessage.error(`文件 ${file.name} 超过 5MB`);
+    input.value = '';
+    return;
+  }
+  await uploadNameplatePhoto(file);
+  input.value = '';
+};
+
+const uploadNameplatePhoto = async (file: File) => {
+  try {
+    const res: any = await uploadFile(file);
+    const url: string | undefined = res?.url || res?.data?.url;
+    const originalName: string = res?.data?.originalName || file.name;
+    if (url) {
+      nameplatePhotoPreview.value = url;
+      form.value.nameplateUrl = url;
+      form.value.nameplateName = originalName;
+      ElMessage.success(`${file.name} 上传成功`);
+    } else {
+      ElMessage.error(`${file.name} 上传失败`);
+    }
+  } catch (e: any) {
+    ElMessage.error(`${file.name} 上传失败: ${e?.message || '网络异常'}`);
+  }
+};
+
+const removeNameplatePhoto = () => {
+  nameplatePhotoPreview.value = '';
+  form.value.nameplateUrl = '';
+  form.value.nameplateName = '';
+};
+
+const clearNameplatePhoto = () => {
+  removeNameplatePhoto();
+};
+
 // ============ 维护照片上传 ============
 const triggerMaintainUpload = () => {
   maintainPhotoInputRef.value?.click();
@@ -776,10 +872,13 @@ const reset = () => {
     equipmentName: '',
     equipmentType: '',
     location: '',
-    photoList: []
+    photoList: [],
+    nameplateUrl: '',
+    nameplateName: ''
   } as any;
   equipmentPhotoPreview.value = [];
   equipmentPhotoList.value = [];
+  nameplatePhotoPreview.value = '';
   formRef.value?.resetFields();
 };