From a30370cd7080d432462ab5aa5986ac10dadf852f Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期一, 18 五月 2026 16:29:36 +0800
Subject: [PATCH] 增加项目部件

---
 src/views/equipmentManagement/repair/Modal/RepairModal.vue |  491 ++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 452 insertions(+), 39 deletions(-)

diff --git a/src/views/equipmentManagement/repair/Modal/RepairModal.vue b/src/views/equipmentManagement/repair/Modal/RepairModal.vue
index 586960d..8b11db6 100644
--- a/src/views/equipmentManagement/repair/Modal/RepairModal.vue
+++ b/src/views/equipmentManagement/repair/Modal/RepairModal.vue
@@ -1,24 +1,176 @@
 <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="880px"
+    @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" placeholder="璇烽�夋嫨" @change="setDeviceModel" filterable style="width: 100%">
+              <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-row>
+      <el-row>
+        <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" disabled placeholder="褰撳墠鐧诲綍浜�" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="楠屾敹浜�">
+            <el-select
+              v-model="form.acceptanceName"
+              placeholder="璇烽�夋嫨楠屾敹浜�"
+              filterable
+              clearable
+              style="width: 100%"
+            >
+              <el-option
+                v-for="item in userList"
+                :key="item.userId ?? item.nickName"
+                :label="item.nickName"
+                :value="item.nickName"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="缁翠慨浜�" required>
+            <el-select
+              v-model="form.maintenanceName"
+              placeholder="璇烽�夋嫨鎴栬緭鍏ョ淮淇汉"
+              filterable
+              allow-create
+              default-first-option
+              clearable
+              style="width: 100%"
+            >
+              <el-option
+                v-for="item in userList"
+                :key="'m-' + (item.userId ?? item.nickName)"
+                :label="item.nickName"
+                :value="item.nickName"
+              />
+            </el-select>
+          </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 v-if="id" 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="beforeAttachmentUpload"
+            >
+              <el-icon class="repair-upload-icon"><UploadFilled /></el-icon>
+              <div class="el-upload__text">灏嗘枃浠舵嫋鍒版澶勶紝鎴� <em>鐐瑰嚮閫夋嫨鏂囦欢</em></div>
+              <template #tip>
+                <div class="el-upload__tip">
+                  璁惧闂鍥撅細{{ id ? '缂栬緫鏃跺垹闄ら渶鐐瑰脊绐楀簳閮ㄣ�岀‘璁ゃ�嶅悗鎵嶇敓鏁堬紱' : '' }}鏀寔 png / jpg / jpeg锛屽崟寮犱笉瓒呰繃 50MB锛屾渶澶� 9 寮�
+                </div>
+              </template>
+            </el-upload>
+          </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 { getCurrentInstance, computed } from "vue";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
 import {
   addRepair,
   editRepair,
   getRepairById,
+  getRepairFileList,
+  uploadRepairFile,
+  deleteRepairFile,
 } from "@/api/equipmentManagement/repair";
+import { REPAIR_FILE_TYPE_PROBLEM, isProblemRepairFile } from "@/api/equipmentManagement/repairFileType.js";
 import { ElMessage } from "element-plus";
+import { UploadFilled, Close } 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 { userListNoPage } from "@/api/system/user.js";
 
 defineOptions({
   name: "璁惧鎶ヤ慨寮圭獥",
@@ -26,45 +178,306 @@
 
 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([]);
+const userList = ref([]);
+/** 寰呮彁浜ょ殑鏂板璁惧闂鍥撅紙纭淇濆瓨鍚庝笂浼狅級 */
+const attachmentFileList = ref([]);
+/** 缂栬緫鏃讹細宸插瓨鍦ㄧ殑璁惧闂鍥撅紙鏈嶅姟绔級 */
+const existingProblemImages = ref([]);
+/** 缂栬緫鏃讹細鏍囪寰呭垹闄ょ殑鍥剧墖 id锛屼粎鐐广�岀‘璁ゃ�嶅悗璋冩帴鍙e垹闄� */
+const pendingRemoveProblemIds = ref([]);
+
+const displayedExistingProblems = computed(() =>
+  existingProblemImages.value.filter((img) => !pendingRemoveProblemIds.value.includes(img.id))
+);
+
+const getFileAccessUrlForModal = (file = {}) => {
+  let raw = file?.link || file?.url || "";
+  if (!raw) return "";
+  if (String(raw).startsWith("http")) return raw;
+  const javaApi = getCurrentInstance()?.proxy?.javaApi || "";
+  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("http")) {
+    if (!fileUrl.startsWith("/")) fileUrl = "/" + fileUrl;
+    fileUrl = javaApi + fileUrl;
+  }
+  return fileUrl;
+};
+
+const markProblemRemove = (fileId) => {
+  if (!pendingRemoveProblemIds.value.includes(fileId)) {
+    pendingRemoveProblemIds.value.push(fileId);
+  }
+};
+
+const previewProblem = (url) => {
+  window.open(url, "_blank");
+};
+
+const loadExistingProblemImages = async (repairId) => {
+  existingProblemImages.value = [];
+  pendingRemoveProblemIds.value = [];
+  if (!repairId) return;
+  try {
+    const res = await getRepairFileList(repairId);
+    const list = Array.isArray(res?.data) ? res.data : [];
+    existingProblemImages.value = list
+      .filter((item) => isProblemRepairFile(item.type))
+      .map((item) => ({
+        id: item.id,
+        name: item.name,
+        url: getFileAccessUrlForModal(item),
+      }));
+  } catch {
+    existingProblemImages.value = [];
+  }
+};
+
+const resetProblemAttachmentState = () => {
+  existingProblemImages.value = [];
+  pendingRemoveProblemIds.value = [];
+};
+const ATTACH_MAX_MB = 50;
+
+const loadDeviceName = async () => {
+  const { data } = await getDeviceLedger();
+  deviceOptions.value = data;
+};
+
+const loadUserList = async () => {
+  const res = await userListNoPage();
+  userList.value = Array.isArray(res?.data) ? res.data : [];
+};
+
+/** 鎶ヤ慨浜哄浐瀹氫负褰撳墠鐧诲綍鐢ㄦ埛锛堜笉鍙敼锛� */
+const syncRepairNameToLoginUser = () => {
+  form.repairName = userStore.nickName || "";
+};
+
+const beforeAttachmentUpload = (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 clearAttachmentQueue = () => {
+  attachmentFileList.value = [];
+};
+
+const applyPendingProblemDeletes = async () => {
+  for (const fileId of pendingRemoveProblemIds.value) {
+    const { code } = await deleteRepairFile(fileId);
+    if (code !== 200) {
+      throw new Error("鍒犻櫎璁惧闂鍥剧墖澶辫触");
+    }
+  }
+  pendingRemoveProblemIds.value = [];
+};
+
+const uploadQueuedRepairImages = async (repairId) => {
+  const rows = attachmentFileList.value || [];
+  const files = rows.map((f) => f.raw).filter(Boolean);
+  if (!files.length || repairId == null) return;
+  for (const file of files) {
+    const fd = new FormData();
+    fd.append("file", file);
+    fd.append("deviceRepairId", String(repairId));
+    fd.append("fileType", String(REPAIR_FILE_TYPE_PROBLEM));
+    const res = await uploadRepairFile(fd);
+    if (res.code !== 200) {
+      throw new Error(res.msg || "闄勪欢涓婁紶澶辫触");
+    }
+  }
+};
+
+const { form, resetForm } = useFormData({
+  deviceLedgerId: undefined, // 璁惧Id
+  deviceName: undefined, // 璁惧鍚嶇О
+  deviceModel: undefined, // 瑙勬牸鍨嬪彿
+  repairTime: dayjs().format("YYYY-MM-DD"), // 鎶ヤ慨鏃ユ湡锛岄粯璁ゅ綋澶�
+  repairName: userStore.nickName, // 鎶ヤ慨浜�
+  acceptanceName: undefined, // 楠屾敹浜�
+  maintenanceName: undefined, // 缁翠慨浜猴紙鍙�夛紝涓庡垪琛ㄧ淮淇汉鍚屾簮锛�
+  remark: undefined, // 鏁呴殰鐜拌薄
+  status: 0, // 鎶ヤ慨鐘舵��
+});
+
+const setDeviceModel = (deviceId) => {
+  const option = deviceOptions.value.find((item) => item.id === deviceId);
+  form.deviceModel = option?.deviceModel;
+};
+
+const setForm = (data) => {
+  form.deviceLedgerId = data.deviceLedgerId;
+  form.deviceName = data.deviceName;
+  form.deviceModel = data.deviceModel;
+  form.repairTime = data.repairTime;
+  form.acceptanceName = data.acceptanceName;
+  form.maintenanceName = data.maintenanceName;
+  form.remark = data.remark;
+  form.status = data.status;
+  syncRepairNameToLoginUser();
+};
 
 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");
+  syncRepairNameToLoginUser();
+  if (!form.deviceLedgerId) {
+    ElMessage.warning("璇烽�夋嫨璁惧鍚嶇О");
+    return;
   }
-  loading.value = false;
+  if (!form.acceptanceName) {
+    ElMessage.warning("璇烽�夋嫨楠屾敹浜�");
+    return;
+  }
+  if (!form.maintenanceName) {
+    ElMessage.warning("璇烽�夋嫨鎴栬緭鍏ョ淮淇汉");
+    return;
+  }
+  loading.value = true;
+  try {
+    let repairId = id.value ? unref(id) : undefined;
+    if (repairId) {
+      const { code } = await editRepair({ id: repairId, ...form });
+      if (code !== 200) return;
+      await applyPendingProblemDeletes();
+    } else {
+      const res = await addRepair(form);
+      if (res.code !== 200) return;
+      repairId = res.data;
+      if (repairId == null && attachmentFileList.value.length) {
+        ElMessage.error("淇濆瓨鎴愬姛浣嗘湭杩斿洖鎶ヤ慨鍗曠紪鍙凤紝鏃犳硶涓婁紶闄勪欢");
+        return;
+      }
+    }
+    if (attachmentFileList.value.length && repairId != null) {
+      await uploadQueuedRepairImages(repairId);
+    }
+    ElMessage.success(`${id.value ? "缂栬緫" : "鏂板"}鎶ヤ慨鎴愬姛`);
+    clearAttachmentQueue();
+    resetProblemAttachmentState();
+    visible.value = false;
+    emits("ok");
+  } catch (e) {
+    ElMessage.error(e?.message || "淇濆瓨鎴栦笂浼犻檮浠跺け璐�");
+  } finally {
+    loading.value = false;
+  }
 };
 
-const openEdit = async (id) => {
-  const { data } = await getRepairById(id);
-  openModal(id);
+const handleCancel = () => {
+  resetForm();
+  syncRepairNameToLoginUser();
+  clearAttachmentQueue();
+  resetProblemAttachmentState();
+  visible.value = false;
+};
+
+const handleClose = () => {
+  resetForm();
+  syncRepairNameToLoginUser();
+  clearAttachmentQueue();
+  resetProblemAttachmentState();
+  visible.value = false;
+};
+
+const openAdd = async () => {
+  id.value = undefined;
+  clearAttachmentQueue();
+  resetProblemAttachmentState();
+  visible.value = true;
   await nextTick();
-  await repairFormRef.value.setForm(data);
+  await Promise.all([loadDeviceName(), loadUserList()]);
+  syncRepairNameToLoginUser();
 };
 
-const close = () => {
-  repairFormRef.value.resetForm();
-  closeModal();
+const openEdit = async (editId) => {
+  clearAttachmentQueue();
+  resetProblemAttachmentState();
+  const { data } = await getRepairById(editId);
+  id.value = editId;
+  visible.value = true;
+  await nextTick();
+  await Promise.all([loadDeviceName(), loadUserList(), loadExistingProblemImages(editId)]);
+  setForm(data);
 };
 
 defineExpose({
-  openModal,
+  openAdd,
   openEdit,
 });
 </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>

--
Gitblit v1.9.3