liding
2026-03-17 5cef24f0ef402b2dd9f16a7f204565da1667cff9
src/views/equipmentManagement/repair/Modal/RepairModal.vue
@@ -1,24 +1,117 @@
<template>
  <el-dialog v-model="visible" :title="modalOptions.title" @close="close">
    <RepairForm ref="repairFormRef" />
    <template #footer>
      <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button>
      <el-button type="primary" @click="sendForm" :loading="loading">
        {{ modalOptions.confirmText }}
      </el-button>
    </template>
  </el-dialog>
  <FormDialog
    v-model="visible"
    :title="id ? '编辑设备报修' : '新增设备报修'"
    width="800px"
    @confirm="sendForm"
    @cancel="handleCancel"
    @close="handleClose"
  >
    <el-form :model="form" label-width="100px">
      <el-row>
        <el-col :span="12">
          <el-form-item label="设备名称">
            <el-select v-model="form.deviceLedgerId" @change="setDeviceModel" filterable>
              <el-option
                v-for="(item, index) in deviceOptions"
                :key="index"
                :label="item.deviceName"
                :value="item.id"
              ></el-option>
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="规格型号">
            <el-input
              v-model="form.deviceModel"
              placeholder="请输入规格型号"
              disabled
            />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="报修日期">
            <el-date-picker
              v-model="form.repairTime"
              placeholder="请选择报修日期"
              format="YYYY-MM-DD"
              value-format="YYYY-MM-DD"
              type="date"
              clearable
              style="width: 100%"
            />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="报修人">
            <el-input v-model="form.repairName" placeholder="请输入报修人" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row v-if="id">
        <el-col :span="12">
          <el-form-item label="报修状态">
            <el-select v-model="form.status">
              <el-option label="待维修" :value="0"></el-option>
              <el-option label="完结" :value="1"></el-option>
              <el-option label="失败" :value="2"></el-option>
            </el-select>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="24">
          <el-form-item label="故障现象">
            <el-input
              v-model="form.remark"
              :rows="2"
              type="textarea"
              placeholder="请输入故障现象"
            />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="24">
          <el-form-item label="现场照片">
            <div class="repair-upload-area">
              <el-upload
                v-model:file-list="uploadFileList"
                :action="uploadUrl"
                :headers="uploadHeaders"
                accept="image/*"
                list-type="picture-card"
                :limit="uploadLimit"
                :on-success="handleUploadSuccess"
                :on-remove="handleRemoveFile"
                :before-upload="beforeUpload"
              >
                <el-icon><Plus /></el-icon>
              </el-upload>
              <div v-if="repairFileList.length === 0" class="upload-tip">请上传现场照片,最多 {{ uploadLimit }} 张</div>
            </div>
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
  </FormDialog>
</template>
<script setup>
import { useModal } from "@/hooks/useModal";
import RepairForm from "../Form/RepairForm.vue";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import {
  addRepair,
  editRepair,
  getRepairById,
} from "@/api/equipmentManagement/repair";
import { ElMessage } from "element-plus";
import { Plus } from "@element-plus/icons-vue";
import dayjs from "dayjs";
import useFormData from "@/hooks/useFormData";
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
import useUserStore from "@/store/modules/user";
import { getToken } from "@/utils/auth";
defineOptions({
  name: "设备报修弹窗",
@@ -26,45 +119,194 @@
const emits = defineEmits(["ok"]);
const repairFormRef = ref();
const {
  id,
  visible,
  loading,
  openModal,
  modalOptions,
  handleConfirm,
  closeModal,
} = useModal({ title: "设备报修" });
const id = ref();
const visible = ref(false);
const loading = ref(false);
const userStore = useUserStore();
const deviceOptions = ref([]);
// 现场照片上传(与 APP 字段一致:fileList / commonFileList)
const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/file/upload";
const uploadLimit = 10;
const uploadFileList = ref([]);
const repairFileList = ref([]);
const uploadHeaders = { Authorization: "Bearer " + getToken() };
const baseApi = import.meta.env.VITE_APP_BASE_API || "";
const formatFileUrl = (url) => {
  if (!url) return "";
  if (url.startsWith("http://") || url.startsWith("https://")) return url;
  const isLocalAbsolutePath = /^[a-zA-Z]:\\|^\\|^\//.test(url);
  if (isLocalAbsolutePath) {
    return url;
  }
  const path = url.replace(/^\/+/, "");
  return path ? baseApi + "/" + path : baseApi;
};
const loadDeviceName = async () => {
  const { data } = await getDeviceLedger();
  deviceOptions.value = data;
};
const { form, resetForm } = useFormData({
  deviceLedgerId: undefined,
  deviceName: undefined,
  deviceModel: undefined,
  repairTime: dayjs().format("YYYY-MM-DD"),
  repairName: userStore.nickName,
  remark: undefined,
  status: 0,
});
const setDeviceModel = (deviceId) => {
  const option = deviceOptions.value.find((item) => item.id === deviceId);
  form.deviceModel = option.deviceModel;
};
const beforeUpload = (file) => {
  const isImage = file.type.startsWith("image/");
  if (!isImage) {
    ElMessage.warning("只能上传图片");
    return false;
  }
  if (repairFileList.value.length >= uploadLimit) {
    ElMessage.warning(`最多上传 ${uploadLimit} 张图片`);
    return false;
  }
  return true;
};
const handleUploadSuccess = (res, file) => {
  if (res?.code !== 200 && res?.code !== 0) {
    ElMessage.error(res?.msg || "上传失败");
    const idx = uploadFileList.value.findIndex((f) => f.uid === file.uid);
    if (idx > -1) uploadFileList.value.splice(idx, 1);
    return;
  }
  const d = res?.data !== undefined ? res.data : res;
  if (!d) return;
  const item = {
    uid: file.uid,
    id: d.id,
    url: d.url || d.downloadUrl || d.tempPath,
    downloadUrl: d.downloadUrl || d.url,
    bucketFilename: d.bucketFilename || d.originalFilename || file.name,
    originalFilename: d.originalFilename || d.bucketFilename || file.name,
    name: d.bucketFilename || d.originalFilename || file.name,
    size: d.size ?? d.byteSize ?? file.size,
    byteSize: d.byteSize ?? d.size ?? file.size,
    uploadResponse: res,
  };
  repairFileList.value.push(item);
};
const handleRemoveFile = (file) => {
  const targetUrl = file.url || file.response?.data?.url || file.response?.data?.downloadUrl;
  repairFileList.value = repairFileList.value.filter(
    (f) => f.uid !== file.uid && (f.url || f.downloadUrl) !== targetUrl
  );
};
const setForm = (data) => {
  form.deviceLedgerId = data.deviceLedgerId;
  form.deviceName = data.deviceName;
  form.deviceModel = data.deviceModel;
  form.repairTime = data.repairTime;
  form.repairName = data.repairName;
  form.remark = data.remark;
  form.status = data.status;
  const list = data.fileList || data.commonFileList || [];
  repairFileList.value = (Array.isArray(list) ? list : []).map((f, i) => ({
    uid: f.id || "existing-" + i,
    id: f.id,
    url: f.url || f.downloadUrl,
    downloadUrl: f.downloadUrl || f.url,
    bucketFilename: f.bucketFilename || f.originalFilename || f.name,
    originalFilename: f.originalFilename || f.bucketFilename || f.name,
    name: f.bucketFilename || f.originalFilename || f.name || "图片",
    size: f.size ?? f.byteSize,
    byteSize: f.byteSize ?? f.size,
    ...f,
  }));
  uploadFileList.value = repairFileList.value.map((f, i) => ({
    uid: f.uid || "existing-" + i,
    name: f.name || f.bucketFilename || "图片",
    url: formatFileUrl(f.url || f.downloadUrl),
    status: "success",
  }));
};
const sendForm = async () => {
  loading.value = true;
  const form = await repairFormRef.value.getForm();
  const { code } = id.value
    ? await editRepair({ id: unref(id), ...form })
    : await addRepair(form);
  if (code == 200) {
    ElMessage.success(`${id ? "编辑" : "新增"}报修成功`);
    closeModal();
    emits("ok");
  try {
    const fileList = repairFileList.value.map((f) => {
      const d = f.uploadResponse?.data !== undefined ? f.uploadResponse.data : f.uploadResponse;
      return d ? { ...d } : (f.id || f.url ? { ...f } : null);
    }).filter(Boolean);
    const submitData = { ...form };
    if (fileList.length) submitData.fileList = fileList;
    const { code } = id.value
      ? await editRepair({ id: unref(id), ...submitData })
      : await addRepair(submitData);
    if (code == 200) {
      ElMessage.success(`${id.value ? "编辑" : "新增"}报修成功`);
      visible.value = false;
      emits("ok");
    }
  } finally {
    loading.value = false;
  }
  loading.value = false;
};
const openEdit = async (id) => {
  const { data } = await getRepairById(id);
  openModal(id);
const handleCancel = () => {
  resetForm();
  repairFileList.value = [];
  uploadFileList.value = [];
  visible.value = false;
};
const handleClose = () => {
  resetForm();
  repairFileList.value = [];
  uploadFileList.value = [];
  visible.value = false;
};
const openAdd = async () => {
  id.value = undefined;
  repairFileList.value = [];
  uploadFileList.value = [];
  visible.value = true;
  await nextTick();
  await repairFormRef.value.setForm(data);
  await loadDeviceName();
};
const close = () => {
  repairFormRef.value.resetForm();
  closeModal();
const openEdit = async (editId) => {
  const { data } = await getRepairById(editId);
  id.value = editId;
  visible.value = true;
  await nextTick();
  await loadDeviceName();
  setForm(data);
};
defineExpose({
  openModal,
  openAdd,
  openEdit,
});
</script>
<style lang="scss" scoped>
.repair-upload-area {
  :deep(.el-upload-list--picture-card) {
    --el-upload-list-picture-card-size: 100px;
  }
}
.upload-tip {
  font-size: 12px;
  color: var(--el-text-color-secondary);
  margin-top: 8px;
}
</style>