gongchunyi
2026-06-22 492802e4fc1b371ba21a2a490c8dcd67d7c8b29c
src/views/equipmentManagement/ledger/Form.vue
@@ -38,9 +38,9 @@
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="工序" prop="processName">
        <el-form-item label="工序" prop="productProcessId">
          <el-select
            v-model="form.processName"
            v-model="form.productProcessId"
            placeholder="请选择工序"
            clearable
            filterable
@@ -48,9 +48,9 @@
          >
            <el-option
              v-for="item in processOptions"
              :key="item"
              :label="item"
              :value="item"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
@@ -180,14 +180,41 @@
               />
            </el-form-item>
         </el-col>
      <el-col :span="24">
        <el-form-item label="设备图片" prop="deviceImage">
          <div v-if="displayedExistingProblems.length" class="problem-existing-wrap">
            <div v-for="img in displayedExistingProblems" :key="img.id" class="problem-thumb">
              <el-image :src="img.url" fit="cover" class="problem-thumb-img" @click="previewProblem(img.url)" />
              <el-icon class="problem-thumb-remove" @click.stop="markProblemRemove(img.id)"><Close /></el-icon>
            </div>
          </div>
          <el-upload
            class="repair-attachment-upload"
            v-model:file-list="attachmentFileList"
            drag
            multiple
            :auto-upload="false"
            :limit="9"
            accept="image/png,image/jpeg,image/jpg"
            :before-upload="beforeUpload"
          >
            <el-icon class="repair-upload-icon"><UploadFilled /></el-icon>
            <div class="el-upload__text">将文件拖到此处,或 <em>点击选择文件</em></div>
            <template #tip>
              <div class="el-upload__tip">
                支持 png / jpg / jpeg,单张不超过 50MB,最多 9 张
              </div>
            </template>
          </el-upload>
        </el-form-item>
      </el-col>
    </el-row>
  </el-form>
</template>
<script setup>
import useFormData from "@/hooks/useFormData";
// import useUserStore from "@/store/modules/user";
import { getLedgerById } from "@/api/equipmentManagement/ledger";
import { getLedgerById, getLedgerFileList, uploadLedgerFile, deleteLedgerFile } from "@/api/equipmentManagement/ledger";
import { processList } from "@/api/productionManagement/productionProcess";
import dayjs from "dayjs";
import {
@@ -195,7 +222,9 @@
  calculateTaxExclusiveTotalPrice,
} from "@/utils/summarizeTable";
import { ElMessage } from "element-plus";
import { ref, onMounted } from "vue";
import { ref, onMounted, getCurrentInstance } from "vue";
import { UploadFilled, Close } from "@element-plus/icons-vue";
import { getToken } from "@/utils/auth";
defineOptions({
  name: "设备台账表单",
@@ -215,7 +244,6 @@
   deviceName: [{ required: true, trigger: "blur", message: "请输入" }],
   deviceModel: [{ required: true, trigger: "blur", message: "请输入" }],
   type: [{ required: true, trigger: "change", message: "请选择或输入设备类型" }],
   processName: [{ required: true, trigger: "change", message: "请选择工序" }],
   supplierName: [{ required: true, trigger: "blur", message: "请输入" }],
   unit: [{ required: true, trigger: "blur", message: "请输入" }],
   number: [{ required: true, trigger: "blur", message: "请输入" }],
@@ -241,7 +269,7 @@
  deviceModel: undefined, // 规格型号
  deviceBrand: undefined, // 设备品牌
  type: undefined, // 设备类型
  processName: undefined, // 工序
  productProcessId: undefined, // 工序ID
  supplierName: undefined, // 供应商
  storageLocation: undefined, // 存放位置
  isDepr: 2, // 是否启用折旧 1-是 2-否
@@ -257,6 +285,20 @@
   planRuntimeTime: dayjs().format("YYYY-MM-DD"), // 录入日期
});
const attachmentFileList = ref([]);
const existingProblemImages = ref([]);
const pendingRemoveProblemIds = ref([]);
const displayedExistingProblems = computed(() =>
  existingProblemImages.value.filter((img) => !pendingRemoveProblemIds.value.includes(img.id))
);
const markProblemRemove = (fileId) => {
  if (!pendingRemoveProblemIds.value.includes(fileId)) {
    pendingRemoveProblemIds.value.push(fileId);
  }
};
const loadForm = async (id) => {
   if (id) {
      operationType.value = 'edit'
@@ -267,8 +309,13 @@
    form.deviceModel = data.deviceModel;
    form.deviceBrand = data.deviceBrand;
    form.type = data.type;
    form.processName = data.processName || data.process;
    ensureProcessOptionExists(form.processName);
    const processName = data.productProcessName || data.processName || data.process;
    form.productProcessId = data.productProcessId;
    if (!form.productProcessId && processName) {
      const matched = processOptions.value.find(item => item.label === processName);
      form.productProcessId = matched?.value;
    }
    ensureProcessOptionExists(form.productProcessId, processName);
    form.supplierName = data.supplierName;
    form.storageLocation = data.storageLocation;
    form.isDepr = data.isDepr;
@@ -287,12 +334,32 @@
      form.planRuntimeTime = undefined;
    }
  }
  existingProblemImages.value = [];
  pendingRemoveProblemIds.value = [];
  attachmentFileList.value = [];
  if (id) {
    try {
      const res = await getLedgerFileList(id);
      const list = Array.isArray(res?.data) ? res.data : [];
      existingProblemImages.value = list.map((item) => ({
        id: item.id,
        name: item.name,
        url: getFileAccessUrl(item),
      }));
    } catch {
      existingProblemImages.value = [];
    }
  }
};
const ensureProcessOptionExists = (value) => {
  if (!value) return;
  if (!processOptions.value.includes(value)) {
    processOptions.value.push(value);
const ensureProcessOptionExists = (id, name) => {
  if (!id) return;
  if (!processOptions.value.some(item => item.value === id)) {
    processOptions.value.push({
      value: id,
      label: name || `${id}`,
    });
  }
};
@@ -301,9 +368,12 @@
    const { code, data } = await processList({});
    if (code === 200 && Array.isArray(data)) {
      processOptions.value = data
        .map(item => item?.name)
        .filter(Boolean);
      ensureProcessOptionExists(form.processName);
        .filter(item => item?.id !== undefined && item?.id !== null)
        .map(item => ({
          value: item.id,
          label: item?.name || `${item.id}`,
        }));
      ensureProcessOptionExists(form.productProcessId);
    }
  } catch (error) {
    processOptions.value = [];
@@ -349,6 +419,77 @@
  getProcessOptions();
});
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload");
const headers = ref({ Authorization: "Bearer " + getToken() });
const ATTACH_MAX_MB = 50;
const beforeUpload = (rawFile) => {
  const okType = ["image/jpeg", "image/jpg", "image/png"].includes(rawFile.type);
  if (!okType) {
    ElMessage.error("只能上传 png / jpg / jpeg 图片");
    return false;
  }
  const maxBytes = ATTACH_MAX_MB * 1024 * 1024;
  if (rawFile.size > maxBytes) {
    ElMessage.error(`单个文件不能超过 ${ATTACH_MAX_MB}MB`);
    return false;
  }
  return true;
};
const submitFiles = async (ledgerId) => {
  for (const fileId of pendingRemoveProblemIds.value) {
    await deleteLedgerFile(fileId);
  }
  pendingRemoveProblemIds.value = [];
  const rows = attachmentFileList.value || [];
  const files = rows.map((f) => f.raw).filter(Boolean);
  if (!files.length || ledgerId == null) return;
  for (const file of files) {
    const fd = new FormData();
    fd.append("file", file);
    fd.append("deviceLedgerId", String(ledgerId));
    // 16 is the FileNameType for EQUIPMENT_LEDGER that we added
    fd.append("fileType", "16");
    const res = await uploadLedgerFile(fd);
    if (res.code !== 200) {
      throw new Error(res.msg || "附件上传失败");
    }
  }
  attachmentFileList.value = [];
};
const getFileAccessUrl = (file = {}) => {
  let raw = file?.link || file?.url || "";
  if (String(raw).startsWith("http")) return raw;
  const javaApi = getCurrentInstance()?.proxy?.javaApi || import.meta.env.VITE_APP_BASE_API || "/dev-api";
  let fileUrl = raw;
  if (fileUrl.indexOf("\\") > -1) {
    const lowerPath = fileUrl.toLowerCase();
    const uploadPathIndex = lowerPath.indexOf("uploadpath");
    if (uploadPathIndex > -1) {
      fileUrl = fileUrl.substring(uploadPathIndex).replace(/\\/g, "/");
    } else {
      fileUrl = fileUrl.replace(/\\/g, "/");
    }
  }
  fileUrl = fileUrl.replace(/^\/?uploadPath/, "/profile");
  if (fileUrl.startsWith("upload/")) {
    fileUrl = "/profile/" + fileUrl;
  }
  if (!fileUrl.startsWith("http")) {
    if (!fileUrl.startsWith("/")) fileUrl = "/" + fileUrl;
    fileUrl = javaApi + fileUrl;
  }
  return fileUrl;
};
const previewProblem = (url) => {
  window.open(url, "_blank");
};
defineExpose({
  form,
  loadForm,
@@ -356,5 +497,58 @@
  clearValidate,
  resetFormAndValidate,
   formRef,
  submitFiles,
});
</script>
<style lang="scss" scoped>
.repair-attachment-upload {
  width: 100%;
  :deep(.el-upload) {
    width: 100%;
  }
  :deep(.el-upload-dragger) {
    width: 100%;
    padding: 24px 16px;
  }
}
.repair-upload-icon {
  font-size: 48px;
  color: var(--el-color-primary);
  margin-bottom: 8px;
}
.problem-existing-wrap {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 12px;
}
.problem-thumb {
  position: relative;
  width: 88px;
  height: 88px;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid var(--el-border-color);
}
.problem-thumb-img {
  width: 100%;
  height: 100%;
  cursor: pointer;
}
.problem-thumb-remove {
  position: absolute;
  top: 2px;
  right: 2px;
  font-size: 16px;
  color: #fff;
  background: rgba(0, 0, 0, 0.45);
  border-radius: 50%;
  padding: 2px;
  cursor: pointer;
}
</style>