From 51081f3acbeef7e5358e20a653c38b0ed3afbe23 Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期五, 15 五月 2026 19:39:19 +0800
Subject: [PATCH] feat: 设备维修新增报修人、验收人、维修人

---
 src/views/equipmentManagement/repair/Modal/RepairDetailModal.vue |  208 ++++++++++
 src/api/equipmentManagement/repair.js                            |   13 
 src/views/equipmentManagement/repair/Modal/MaintainModal.vue     |  165 ++++++-
 src/api/equipmentManagement/repairFileType.js                    |   11 
 src/views/equipmentManagement/repair/Modal/RepairModal.vue       |  328 +++++++++++++++
 src/views/equipmentManagement/repair/index.vue                   |  309 +++++++++++----
 src/views/equipmentManagement/repair/Modal/AcceptanceModal.vue   |  125 ++++++
 7 files changed, 1,030 insertions(+), 129 deletions(-)

diff --git a/src/api/equipmentManagement/repair.js b/src/api/equipmentManagement/repair.js
index e3fe9b4..15cdf28 100644
--- a/src/api/equipmentManagement/repair.js
+++ b/src/api/equipmentManagement/repair.js
@@ -26,7 +26,7 @@
 
 /**
  * @desc 璁惧鎶ヤ慨-涓婁紶闄勪欢
- * @param {FormData} formData file + deviceRepairId
+ * @param {FormData} formData file + deviceRepairId + fileType(14璁惧闂/15缁翠慨瀹屾垚)
  */
 export const uploadRepairFile = (formData) => {
   return request({
@@ -108,3 +108,14 @@
     data,
   });
 };
+
+/**
+ * @desc 璁惧鎶ヤ慨楠屾敹
+ */
+export const acceptRepair = (data) => {
+  return request({
+    url: `/device/repair/acceptance`,
+    method: "post",
+    data,
+  });
+};
diff --git a/src/api/equipmentManagement/repairFileType.js b/src/api/equipmentManagement/repairFileType.js
new file mode 100644
index 0000000..f49ad5a
--- /dev/null
+++ b/src/api/equipmentManagement/repairFileType.js
@@ -0,0 +1,11 @@
+/** 璁惧鎶ヤ慨闄勪欢绫诲瀷 */
+export const REPAIR_FILE_TYPE_LEGACY = 13;
+export const REPAIR_FILE_TYPE_PROBLEM = 14;
+export const REPAIR_FILE_TYPE_MAINTAIN = 15;
+
+/** 鏄惁璁惧闂绫诲浘鐗� */
+export const isProblemRepairFile = (type) =>
+  type === REPAIR_FILE_TYPE_PROBLEM || type === REPAIR_FILE_TYPE_LEGACY;
+
+/** 鏄惁缁翠慨瀹屾垚绫诲浘鐗� */
+export const isMaintainRepairFile = (type) => type === REPAIR_FILE_TYPE_MAINTAIN;
diff --git a/src/views/equipmentManagement/repair/Modal/AcceptanceModal.vue b/src/views/equipmentManagement/repair/Modal/AcceptanceModal.vue
new file mode 100644
index 0000000..32131a1
--- /dev/null
+++ b/src/views/equipmentManagement/repair/Modal/AcceptanceModal.vue
@@ -0,0 +1,125 @@
+<template>
+  <FormDialog
+    v-model="visible"
+    title="楠屾敹瀹℃壒"
+    width="520px"
+    :loading="loading"
+    @confirm="sendForm"
+    @cancel="handleCancel"
+    @close="handleClose"
+  >
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+      <el-form-item label="楠屾敹浜�" prop="acceptanceName">
+        <el-input v-model="form.acceptanceName" disabled placeholder="鎶ヤ慨鏃舵寚瀹氱殑楠屾敹浜�" />
+      </el-form-item>
+      <el-form-item label="楠屾敹鏃堕棿" prop="acceptanceTime">
+        <el-date-picker
+          v-model="form.acceptanceTime"
+          type="datetime"
+          placeholder="璇烽�夋嫨楠屾敹鏃堕棿"
+          format="YYYY-MM-DD HH:mm:ss"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          style="width: 100%"
+        />
+      </el-form-item>
+      <el-form-item label="楠屾敹澶囨敞" prop="acceptanceRemark">
+        <el-input
+          v-model="form.acceptanceRemark"
+          type="textarea"
+          :rows="3"
+          placeholder="璇疯緭鍏ラ獙鏀跺娉�"
+        />
+      </el-form-item>
+    </el-form>
+  </FormDialog>
+</template>
+
+<script setup>
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+import { acceptRepair } from "@/api/equipmentManagement/repair";
+import useFormData from "@/hooks/useFormData";
+import useUserStore from "@/store/modules/user";
+import dayjs from "dayjs";
+import { ElMessage } from "element-plus";
+
+defineOptions({
+  name: "璁惧鎶ヤ慨楠屾敹寮圭獥",
+});
+
+const emits = defineEmits(["ok"]);
+
+const repairId = ref();
+const visible = ref(false);
+const loading = ref(false);
+const formRef = ref();
+const userStore = useUserStore();
+
+const rules = {
+  acceptanceName: [{ required: true, message: "楠屾敹浜轰笉鑳戒负绌�", trigger: "blur" }],
+  acceptanceTime: [{ required: true, message: "璇烽�夋嫨楠屾敹鏃堕棿", trigger: "change" }],
+  acceptanceRemark: [{ required: true, message: "璇疯緭鍏ラ獙鏀跺娉�", trigger: "blur" }],
+};
+
+const { form, resetForm } = useFormData({
+  acceptanceName: undefined,
+  acceptanceTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
+  acceptanceRemark: undefined,
+});
+
+const setForm = (row) => {
+  form.acceptanceName = row.acceptanceName;
+  form.acceptanceTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
+  form.acceptanceRemark = undefined;
+};
+
+const sendForm = async () => {
+  const valid = await formRef.value?.validate().catch(() => false);
+  if (!valid) return;
+  if (form.acceptanceName !== userStore.nickName) {
+    ElMessage.warning("浠呮寚瀹氱殑楠屾敹浜哄彲杩涜楠屾敹");
+    return;
+  }
+  loading.value = true;
+  try {
+    const { code, msg } = await acceptRepair({
+      id: repairId.value,
+      acceptanceTime: form.acceptanceTime,
+      acceptanceRemark: form.acceptanceRemark,
+    });
+    if (code === 200) {
+      ElMessage.success("楠屾敹鎴愬姛");
+      visible.value = false;
+      emits("ok");
+    } else if (msg) {
+      ElMessage.error(msg);
+    }
+  } finally {
+    loading.value = false;
+  }
+};
+
+const handleCancel = () => {
+  resetForm();
+  visible.value = false;
+};
+
+const handleClose = () => {
+  resetForm();
+  visible.value = false;
+};
+
+const open = async (id, row) => {
+  if (!row?.acceptanceName || row.acceptanceName !== userStore.nickName) {
+    ElMessage.warning("浠呮寚瀹氱殑楠屾敹浜哄彲杩涜楠屾敹");
+    return;
+  }
+  repairId.value = id;
+  visible.value = true;
+  await nextTick();
+  setForm(row);
+};
+
+defineExpose({
+  open,
+});
+</script>
diff --git a/src/views/equipmentManagement/repair/Modal/MaintainModal.vue b/src/views/equipmentManagement/repair/Modal/MaintainModal.vue
index 496b072..ed09da9 100644
--- a/src/views/equipmentManagement/repair/Modal/MaintainModal.vue
+++ b/src/views/equipmentManagement/repair/Modal/MaintainModal.vue
@@ -1,27 +1,27 @@
 <template>
   <FormDialog
     v-model="visible"
-    :title="'璁惧缁翠慨'"
-    width="500px"
+    title="璁惧缁翠慨"
+    width="640px"
+    :loading="loading"
     @confirm="sendForm"
     @cancel="handleCancel"
     @close="handleClose"
   >
-    <el-form :model="form" label-width="80px">
-      <el-form-item label="缁翠慨浜�">
-        <el-input v-model="form.maintenanceName" placeholder="璇疯緭鍏ョ淮淇汉" />
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="90px">
+      <el-form-item label="缁翠慨浜�" prop="maintenanceName">
+        <el-input v-model="form.maintenanceName" disabled placeholder="鎶ヤ慨鏃舵寚瀹氱殑缁翠慨浜�" />
       </el-form-item>
-      <el-form-item label="缁翠慨缁撴灉">
+      <el-form-item label="缁翠慨缁撴灉" prop="maintenanceResult">
         <el-input v-model="form.maintenanceResult" placeholder="璇疯緭鍏ョ淮淇粨鏋�" />
       </el-form-item>
-      <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-form-item label="缁翠慨鐘舵��" prop="status">
+        <el-select v-model="form.status" style="width: 100%">
+          <el-option label="寰呴獙鏀�" :value="3" />
+          <el-option label="缁翠慨澶辫触" :value="2" />
         </el-select>
       </el-form-item>
-      <el-form-item label="缁翠慨鏃ユ湡">
+      <el-form-item label="缁翠慨鏃ユ湡" prop="maintenanceTime">
         <el-date-picker
           v-model="form.maintenanceTime"
           placeholder="璇烽�夋嫨缁翠慨鏃ユ湡"
@@ -32,17 +32,39 @@
           style="width: 100%"
         />
       </el-form-item>
+      <el-form-item label="闄勪欢">
+        <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">
+              鏀寔 png / jpg / jpeg锛屽崟涓笉瓒呰繃 50MB锛屼繚瀛樺悗涓庡垪琛ㄣ�岄檮浠躲�嶅悓姝�
+            </div>
+          </template>
+        </el-upload>
+      </el-form-item>
     </el-form>
   </FormDialog>
 </template>
 
 <script setup>
 import FormDialog from "@/components/Dialog/FormDialog.vue";
-import { addMaintain } from "@/api/equipmentManagement/repair";
+import { addMaintain, uploadRepairFile } from "@/api/equipmentManagement/repair";
+import { REPAIR_FILE_TYPE_MAINTAIN } from "@/api/equipmentManagement/repairFileType.js";
 import useFormData from "@/hooks/useFormData";
 import useUserStore from "@/store/modules/user";
 import dayjs from "dayjs";
 import { ElMessage } from "element-plus";
+import { UploadFilled } from "@element-plus/icons-vue";
 
 defineOptions({
   name: "缁翠慨妯℃�佹",
@@ -50,39 +72,95 @@
 
 const emits = defineEmits(["ok"]);
 
-// 淇濆瓨鎶ヤ慨璁板綍鐨刬d
 const repairId = ref();
 const visible = ref(false);
 const loading = ref(false);
+const formRef = ref();
+const attachmentFileList = ref([]);
+const ATTACH_MAX_MB = 50;
 
 const userStore = useUserStore();
+
+const rules = {
+  maintenanceName: [{ required: true, message: "璇烽�夋嫨缁翠慨浜�", trigger: "change" }],
+  maintenanceResult: [{ required: true, message: "璇疯緭鍏ョ淮淇粨鏋�", trigger: "blur" }],
+  maintenanceTime: [{ required: true, message: "璇烽�夋嫨缁翠慨鏃ユ湡", trigger: "change" }],
+};
+
 const { form, resetForm } = useFormData({
-  maintenanceName: undefined, // 缁翠慨鍚嶇О
-  maintenanceResult: undefined, // 缁翠慨缁撴灉
-  maintenanceTime: undefined, // 缁翠慨鏃ユ湡
-  status: 0,
+  maintenanceName: undefined,
+  maintenanceResult: undefined,
+  maintenanceTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
+  status: 3,
 });
 
+const syncMaintenanceNameFromRow = (row) => {
+  form.maintenanceName = row?.maintenanceName || "";
+};
+
+const beforeAttachmentUpload = (rawFile) => {
+  const okType = ["image/jpeg", "image/jpg", "image/png"].includes(rawFile.type);
+  if (!okType) {
+    ElMessage.error("鍙兘涓婁紶 png / jpg / jpeg 鍥剧墖");
+    return false;
+  }
+  if (rawFile.size > ATTACH_MAX_MB * 1024 * 1024) {
+    ElMessage.error(`鍗曚釜鏂囦欢涓嶈兘瓒呰繃 ${ATTACH_MAX_MB}MB`);
+    return false;
+  }
+  return true;
+};
+
+const clearAttachmentQueue = () => {
+  attachmentFileList.value = [];
+};
+
+const uploadQueuedImages = async (id) => {
+  const files = (attachmentFileList.value || []).map((f) => f.raw).filter(Boolean);
+  for (const file of files) {
+    const fd = new FormData();
+    fd.append("file", file);
+    fd.append("deviceRepairId", String(id));
+    fd.append("fileType", String(REPAIR_FILE_TYPE_MAINTAIN));
+    const res = await uploadRepairFile(fd);
+    if (res.code !== 200) {
+      throw new Error(res.msg || "闄勪欢涓婁紶澶辫触");
+    }
+  }
+};
+
 const setForm = (data) => {
-  form.maintenanceName = data.maintenanceName ?? userStore.nickName;
+  syncMaintenanceNameFromRow(data);
   form.maintenanceResult = data.maintenanceResult;
-  form.maintenanceTime =
-    data.maintenanceTime 
-      ? dayjs(data.maintenanceTime).format("YYYY-MM-DD HH:mm:ss")
-      : dayjs().format("YYYY-MM-DD HH:mm:ss");
-  form.status = 1; // 榛樿鐘舵�佷负瀹岀粨
+  form.maintenanceTime = data.maintenanceTime
+    ? dayjs(data.maintenanceTime).format("YYYY-MM-DD HH:mm:ss")
+    : dayjs().format("YYYY-MM-DD HH:mm:ss");
+  form.status = 3;
 };
 
 const sendForm = async () => {
+  const valid = await formRef.value?.validate().catch(() => false);
+  if (!valid) return;
+  if (form.maintenanceName !== userStore.nickName) {
+    ElMessage.warning("浠呮寚瀹氱殑缁翠慨浜哄彲杩涜缁翠慨");
+    return;
+  }
   loading.value = true;
   try {
-    const { code } = await addMaintain({ id: repairId.value, ...form });
-    if (code == 200) {
-      ElMessage.success("缁翠慨鎴愬姛");
-      emits("ok");
-      resetForm();
-      visible.value = false;
+    const { code, msg } = await addMaintain({ id: repairId.value, ...form });
+    if (code !== 200) {
+      if (msg) ElMessage.error(msg);
+      return;
     }
+    if (attachmentFileList.value.length) {
+      await uploadQueuedImages(repairId.value);
+    }
+    ElMessage.success("缁翠慨鎴愬姛");
+    clearAttachmentQueue();
+    visible.value = false;
+    emits("ok");
+  } catch (e) {
+    ElMessage.error(e?.message || "淇濆瓨鎴栦笂浼犻檮浠跺け璐�");
   } finally {
     loading.value = false;
   }
@@ -90,19 +168,26 @@
 
 const handleCancel = () => {
   resetForm();
+  clearAttachmentQueue();
   visible.value = false;
 };
 
 const handleClose = () => {
   resetForm();
+  clearAttachmentQueue();
   visible.value = false;
 };
 
 const open = async (id, row) => {
-  repairId.value = id; // 淇濆瓨鎶ヤ慨璁板綍鐨刬d
+  repairId.value = id;
+  clearAttachmentQueue();
   visible.value = true;
   await nextTick();
   setForm(row);
+  if (row?.maintenanceName && row.maintenanceName !== userStore.nickName) {
+    ElMessage.warning("浠呮寚瀹氱殑缁翠慨浜哄彲杩涜缁翠慨");
+    visible.value = false;
+  }
 };
 
 defineExpose({
@@ -110,4 +195,20 @@
 });
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+.repair-attachment-upload {
+  width: 100%;
+  :deep(.el-upload) {
+    width: 100%;
+  }
+  :deep(.el-upload-dragger) {
+    width: 100%;
+    padding: 20px 16px;
+  }
+}
+.repair-upload-icon {
+  font-size: 42px;
+  color: var(--el-color-primary);
+  margin-bottom: 8px;
+}
+</style>
diff --git a/src/views/equipmentManagement/repair/Modal/RepairDetailModal.vue b/src/views/equipmentManagement/repair/Modal/RepairDetailModal.vue
new file mode 100644
index 0000000..7735f5b
--- /dev/null
+++ b/src/views/equipmentManagement/repair/Modal/RepairDetailModal.vue
@@ -0,0 +1,208 @@
+<template>
+  <el-dialog
+    v-model="visible"
+    title="鎶ヤ慨鍏ㄨ繃绋�"
+    width="820px"
+    destroy-on-close
+    @closed="onClosed"
+  >
+    <div v-loading="loading" class="repair-detail">
+      <template v-if="detail">
+        <div class="phase-block">
+          <div class="phase-title">1. 鎶ヤ慨鐧昏</div>
+          <el-descriptions :column="2" border size="small">
+            <el-descriptions-item label="璁惧鍚嶇О">{{ detail.deviceName || "鈥�" }}</el-descriptions-item>
+            <el-descriptions-item label="瑙勬牸鍨嬪彿">{{ detail.deviceModel || "鈥�" }}</el-descriptions-item>
+            <el-descriptions-item label="鎶ヤ慨鏃ユ湡">{{ fmtDate(detail.repairTime) }}</el-descriptions-item>
+            <el-descriptions-item label="鎶ヤ慨浜�">{{ detail.repairName || "鈥�" }}</el-descriptions-item>
+            <el-descriptions-item label="楠屾敹浜�">{{ detail.acceptanceName || "鈥�" }}</el-descriptions-item>
+            <el-descriptions-item label="缁翠慨浜�">{{ detail.maintenanceName || "鈥�" }}</el-descriptions-item>
+            <el-descriptions-item label="鏁呴殰鐜拌薄" :span="2">{{ detail.remark || "鈥�" }}</el-descriptions-item>
+            <el-descriptions-item label="褰撳墠鐘舵��" :span="2">
+              <el-tag v-if="detail.status === 0" type="warning" size="small">寰呯淮淇�</el-tag>
+              <el-tag v-else-if="detail.status === 3" type="info" size="small">寰呴獙鏀�</el-tag>
+              <el-tag v-else-if="detail.status === 1" type="success" size="small">瀹屾垚</el-tag>
+              <el-tag v-else-if="detail.status === 2" type="danger" size="small">缁翠慨澶辫触</el-tag>
+              <span v-else>鈥�</span>
+            </el-descriptions-item>
+          </el-descriptions>
+          <div class="img-row-label">璁惧闂鍥剧墖</div>
+          <div v-if="problemImages.length" class="img-grid">
+            <el-image
+              v-for="img in problemImages"
+              :key="img.id"
+              :src="img.url"
+              fit="cover"
+              class="thumb"
+              :preview-src-list="problemImages.map((i) => i.url)"
+              preview-teleported
+            />
+          </div>
+          <el-empty v-else description="鏆傛棤璁惧闂鍥剧墖" :image-size="56" />
+        </div>
+
+        <div v-if="hasMaintenanceStep" class="phase-block">
+          <div class="phase-title">2. 缁翠慨澶勭悊</div>
+          <el-descriptions :column="2" border size="small">
+            <el-descriptions-item label="缁翠慨浜�">{{ detail.maintenanceName || "鈥�" }}</el-descriptions-item>
+            <el-descriptions-item label="缁翠慨鏃堕棿">{{ fmtDateTime(detail.maintenanceTime) }}</el-descriptions-item>
+            <el-descriptions-item label="缁翠慨缁撴灉" :span="2">{{ detail.maintenanceResult || "鈥�" }}</el-descriptions-item>
+          </el-descriptions>
+          <div v-if="maintainImages.length" class="img-row-label">缁翠慨瀹屾垚鍥剧墖</div>
+          <div v-if="maintainImages.length" class="img-grid">
+            <el-image
+              v-for="img in maintainImages"
+              :key="img.id"
+              :src="img.url"
+              fit="cover"
+              class="thumb"
+              :preview-src-list="maintainImages.map((i) => i.url)"
+              preview-teleported
+            />
+          </div>
+        </div>
+
+        <div v-if="detail.status === 1" class="phase-block">
+          <div class="phase-title">3. 楠屾敹</div>
+          <el-descriptions :column="2" border size="small">
+            <el-descriptions-item label="楠屾敹浜�">{{ detail.acceptanceName || "鈥�" }}</el-descriptions-item>
+            <el-descriptions-item label="楠屾敹鏃堕棿">{{ fmtDateTime(detail.acceptanceTime) }}</el-descriptions-item>
+            <el-descriptions-item label="楠屾敹澶囨敞" :span="2">{{ detail.acceptanceRemark || "鈥�" }}</el-descriptions-item>
+          </el-descriptions>
+        </div>
+      </template>
+    </div>
+    <template #footer>
+      <el-button type="primary" @click="visible = false">鍏抽棴</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { computed } from "vue";
+import dayjs from "dayjs";
+import { getRepairById, getRepairFileList } from "@/api/equipmentManagement/repair";
+import { isProblemRepairFile, isMaintainRepairFile } from "@/api/equipmentManagement/repairFileType.js";
+
+defineOptions({ name: "RepairDetailModal" });
+
+const props = defineProps({
+  /** 涓庡垪琛ㄩ〉銆侀檮浠跺脊绐椾竴鑷达紝鐢ㄤ簬鎷奸潤鎬佽祫婧愬畬鏁村湴鍧�锛堝瓙缁勪欢鏃犲叏灞� proxy 鏃堕渶鐢辩埗椤典紶鍏ワ級 */
+  javaApi: {
+    type: String,
+    default: "",
+  },
+});
+
+const visible = ref(false);
+const loading = ref(false);
+const detail = ref(null);
+const problemImages = ref([]);
+const maintainImages = ref([]);
+
+const apiBase = computed(
+  () => props.javaApi || import.meta.env.VITE_APP_BASE_API || ""
+);
+
+const hasMaintenanceStep = computed(() => {
+  const s = detail.value?.status;
+  return s === 2 || s === 3 || s === 1;
+});
+
+const fmtDate = (v) => (v ? dayjs(v).format("YYYY-MM-DD") : "鈥�");
+const fmtDateTime = (v) => (v ? dayjs(v).format("YYYY-MM-DD HH:mm:ss") : "鈥�");
+
+/** 涓庤澶囨姤淇� index.vue 涓� getFileAccessUrl + normalizeFileUrl 閫昏緫涓�鑷� */
+const buildFileUrl = (file = {}) => {
+  let raw = "";
+  if (file.link) {
+    if (String(file.link).startsWith("http")) return file.link;
+    raw = file.link;
+  } else {
+    raw = file.url || "";
+  }
+  if (!raw) return "";
+  if (String(raw).startsWith("http")) return raw;
+  let fileUrl = raw;
+  if (fileUrl && 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 = apiBase.value + fileUrl;
+  }
+  return fileUrl;
+};
+
+const open = async (id) => {
+  if (!id) return;
+  visible.value = true;
+  loading.value = true;
+  detail.value = null;
+  problemImages.value = [];
+  maintainImages.value = [];
+  try {
+    const { data } = await getRepairById(id);
+    detail.value = data || null;
+    const fileRes = await getRepairFileList(id);
+    const list = Array.isArray(fileRes?.data) ? fileRes.data : [];
+    problemImages.value = list
+      .filter((f) => isProblemRepairFile(f.type))
+      .map((f) => ({ id: f.id, url: buildFileUrl(f) }));
+    maintainImages.value = list
+      .filter((f) => isMaintainRepairFile(f.type))
+      .map((f) => ({ id: f.id, url: buildFileUrl(f) }));
+  } finally {
+    loading.value = false;
+  }
+};
+
+const onClosed = () => {
+  detail.value = null;
+  problemImages.value = [];
+  maintainImages.value = [];
+};
+
+defineExpose({ open });
+</script>
+
+<style scoped>
+.repair-detail {
+  min-height: 120px;
+}
+.phase-block {
+  margin-bottom: 20px;
+}
+.phase-title {
+  font-size: 15px;
+  font-weight: 600;
+  color: #303133;
+  margin-bottom: 10px;
+  padding-left: 8px;
+  border-left: 3px solid var(--el-color-primary);
+}
+.img-row-label {
+  font-size: 13px;
+  color: #606266;
+  margin: 14px 0 8px;
+  font-weight: 500;
+}
+.img-grid {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+}
+.thumb {
+  width: 88px;
+  height: 88px;
+  border-radius: 6px;
+  border: 1px solid var(--el-border-color);
+}
+</style>
diff --git a/src/views/equipmentManagement/repair/Modal/RepairModal.vue b/src/views/equipmentManagement/repair/Modal/RepairModal.vue
index 1aa82ec..ab64cbf 100644
--- a/src/views/equipmentManagement/repair/Modal/RepairModal.vue
+++ b/src/views/equipmentManagement/repair/Modal/RepairModal.vue
@@ -2,7 +2,7 @@
   <FormDialog
     v-model="visible"
     :title="id ? '缂栬緫璁惧鎶ヤ慨' : '鏂板璁惧鎶ヤ慨'"
-    width="800px"
+    width="880px"
     @confirm="sendForm"
     @cancel="handleCancel"
     @close="handleClose"
@@ -11,7 +11,7 @@
       <el-row>
         <el-col :span="12">
           <el-form-item label="璁惧鍚嶇О">
-            <el-select v-model="form.deviceLedgerId" @change="setDeviceModel" filterable>
+            <el-select v-model="form.deviceLedgerId" placeholder="璇烽�夋嫨" @change="setDeviceModel" filterable style="width: 100%">
               <el-option
                 v-for="(item, index) in deviceOptions"
                 :key="index"
@@ -30,6 +30,8 @@
             />
           </el-form-item>
         </el-col>
+      </el-row>
+      <el-row>
         <el-col :span="12">
           <el-form-item label="鎶ヤ慨鏃ユ湡">
             <el-date-picker
@@ -45,7 +47,45 @@
         </el-col>
         <el-col :span="12">
           <el-form-item label="鎶ヤ慨浜�">
-            <el-input v-model="form.repairName" placeholder="璇疯緭鍏ユ姤淇汉" />
+            <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
+              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>
@@ -72,22 +112,63 @@
           </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 { 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: "璁惧鎶ヤ慨寮圭獥",
@@ -101,10 +182,133 @@
 
 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({
@@ -113,13 +317,15 @@
   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;
+  form.deviceModel = option?.deviceModel;
 };
 
 const setForm = (data) => {
@@ -127,22 +333,53 @@
   form.deviceName = data.deviceName;
   form.deviceModel = data.deviceModel;
   form.repairTime = data.repairTime;
-  form.repairName = data.repairName;
+  form.acceptanceName = data.acceptanceName;
+  form.maintenanceName = data.maintenanceName;
   form.remark = data.remark;
   form.status = data.status;
+  syncRepairNameToLoginUser();
 };
 
 const sendForm = async () => {
+  syncRepairNameToLoginUser();
+  if (!form.deviceLedgerId) {
+    ElMessage.warning("璇烽�夋嫨璁惧鍚嶇О");
+    return;
+  }
+  if (!form.acceptanceName) {
+    ElMessage.warning("璇烽�夋嫨楠屾敹浜�");
+    return;
+  }
+  if (!form.maintenanceName) {
+    ElMessage.warning("璇烽�夋嫨缁翠慨浜�");
+    return;
+  }
   loading.value = true;
   try {
-    const { code } = id.value
-      ? await editRepair({ id: unref(id), ...form })
-      : await addRepair(form);
-    if (code == 200) {
-      ElMessage.success(`${id.value ? "缂栬緫" : "鏂板"}鎶ヤ慨鎴愬姛`);
-      visible.value = false;
-      emits("ok");
+    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;
   }
@@ -150,27 +387,38 @@
 
 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 loadDeviceName();
+  await Promise.all([loadDeviceName(), loadUserList()]);
+  syncRepairNameToLoginUser();
 };
 
 const openEdit = async (editId) => {
+  clearAttachmentQueue();
+  resetProblemAttachmentState();
   const { data } = await getRepairById(editId);
   id.value = editId;
   visible.value = true;
   await nextTick();
-  await loadDeviceName();
+  await Promise.all([loadDeviceName(), loadUserList(), loadExistingProblemImages(editId)]);
   setForm(data);
 };
 
@@ -180,4 +428,54 @@
 });
 </script>
 
-<style lang="scss" scoped></style>
+<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>
diff --git a/src/views/equipmentManagement/repair/index.vue b/src/views/equipmentManagement/repair/index.vue
index e14ab37..3f48df5 100644
--- a/src/views/equipmentManagement/repair/index.vue
+++ b/src/views/equipmentManagement/repair/index.vue
@@ -98,74 +98,109 @@
           @pagination="changePage"
       >
         <template #statusRef="{ row }">
-          <el-tag v-if="row.status === 2" type="danger">澶辫触</el-tag>
-          <el-tag v-if="row.status === 1" type="success">瀹岀粨</el-tag>
           <el-tag v-if="row.status === 0" type="warning">寰呯淮淇�</el-tag>
+          <el-tag v-else-if="row.status === 3" type="info">寰呴獙鏀�</el-tag>
+          <el-tag v-else-if="row.status === 1" type="success">瀹屾垚</el-tag>
+          <el-tag v-else-if="row.status === 2" type="danger">缁翠慨澶辫触</el-tag>
         </template>
         <template #operation="{ row }">
-          <el-button
-            type="primary"
-            link
-            :disabled="row.status === 1"
-            @click="editRepair(row.id)"
-          >
+          <el-button type="info" link @click="openRepairDetail(row.id)">
+            璇︽儏
+          </el-button>
+          <el-button type="primary" link :disabled="!canEdit(row)" @click="editRepair(row.id)">
             缂栬緫
           </el-button>
-          <el-button
-            type="info"
-            link
-            :disabled="row.status === 1"
-            @click="openAttachment(row)"
-          >
-            闄勪欢
-          </el-button>
-          <el-button
-            type="success"
-            link
-            :disabled="row.status === 1"
-            @click="addMaintain(row)"
-          >
+          <el-button type="success" link :disabled="!canMaintain(row)" @click="addMaintain(row)">
             缁翠慨
           </el-button>
-          <el-button
-            type="danger"
-            link
-            :disabled="row.status === 1"
-            @click="delRepairByIds(row.id)"
-          >
+          <el-button type="warning" link :disabled="!canAccept(row)" @click="openAcceptance(row)">
+            楠屾敹
+          </el-button>
+          <el-button type="danger" link :disabled="!canDelete(row)" @click="delRepairByIds(row.id)">
             鍒犻櫎
+          </el-button>
+          <el-button type="primary" link @click="openAttachment(row)">
+            闄勪欢
           </el-button>
         </template>
       </PIMTable>
     </div>
     <RepairModal ref="repairModalRef" @ok="getTableData"/>
     <MaintainModal ref="maintainModalRef" @ok="getTableData"/>
+    <AcceptanceModal ref="acceptanceModalRef" @ok="getTableData"/>
+    <RepairDetailModal ref="repairDetailModalRef" :java-api="javaApi" />
 
-    <el-dialog v-model="attachment.visible" title="闄勪欢" width="860px" @closed="onAttachmentClosed">
+    <el-dialog v-model="attachment.visible" title="闄勪欢" width="900px" @closed="onAttachmentClosed">
       <div v-loading="attachment.loading" class="attachment-wrap">
-        <div class="attachment-actions">
-          <el-upload
-            v-model:file-list="attachment.fileList"
-            :action="upload.url"
-            multiple
-            ref="attachmentFileUpload"
-            auto-upload
-            :headers="upload.headers"
-            :data="{ deviceRepairId: attachment.deviceRepairId }"
-            :before-upload="handleAttachmentBeforeUpload"
-            :on-error="handleAttachmentUploadError"
-            :on-success="handleAttachmentUploadSuccess"
-            :on-preview="handleAttachmentPreview"
-            :on-remove="handleAttachmentRemove"
-            list-type="picture-card"
-            :limit="9"
-            accept="image/png,image/jpeg,image/jpg"
-          >
-            +
-          </el-upload>
+        <p v-if="attachmentReadOnly" class="attachment-readonly-tip">
+          宸插紑濮嬬淮淇紝姝ゅ浠呭彲鏌ョ湅锛涜澶囬棶棰樺浘璇峰湪鎶ヤ慨銆屽緟缁翠慨銆嶆椂浜庛�岀紪杈戙�嶄腑缁存姢锛涚淮淇畬鎴愬浘璇峰湪銆岀淮淇�嶆椂涓婁紶銆�
+        </p>
+        <div class="attachment-section">
+          <div class="attachment-section-title">璁惧闂鍥剧墖</div>
+          <template v-if="attachmentReadOnly">
+            <div v-if="attachment.problemFileList.length" class="attachment-readonly-grid">
+              <div
+                v-for="f in attachment.problemFileList"
+                :key="f.id"
+                class="attachment-readonly-item"
+                @click="handleAttachmentPreview(f)"
+              >
+                <el-image :src="f.url" fit="cover" class="attachment-readonly-img" />
+              </div>
+            </div>
+            <el-empty
+              v-else
+              description="鏆傛棤璁惧闂鍥剧墖"
+              :image-size="60"
+            />
+          </template>
+          <template v-else>
+            <el-upload
+              v-model:file-list="attachment.problemFileList"
+              :action="upload.url"
+              multiple
+              auto-upload
+              :headers="upload.headers"
+              :data="uploadDataProblem"
+              :before-upload="handleAttachmentBeforeUpload"
+              :on-error="handleAttachmentUploadError"
+              :on-success="() => handleAttachmentUploadSuccess('problem')"
+              :on-preview="handleAttachmentPreview"
+              :on-remove="(file) => handleAttachmentRemove(file, 'problem')"
+              list-type="picture-card"
+              :limit="9"
+              accept="image/png,image/jpeg,image/jpg"
+            >
+              +
+            </el-upload>
+            <el-empty
+              v-if="!attachment.loading && attachment.problemFileList.length === 0"
+              description="鏆傛棤璁惧闂鍥剧墖"
+              :image-size="60"
+            />
+          </template>
         </div>
-
-        <el-empty v-if="!attachment.loading && attachment.files.length === 0" description="鏆傛棤闄勪欢" />
+        <div class="attachment-section">
+          <div class="attachment-section-title">缁翠慨瀹屾垚鍥剧墖</div>
+          <p v-if="!attachmentReadOnly" class="attachment-readonly-tip" style="margin-top: 0">
+            缁翠慨瀹屾垚鍥捐鍦ㄥ垪琛ㄣ�岀淮淇�嶆搷浣滀腑涓婁紶锛屾澶勪粎鏌ョ湅銆�
+          </p>
+          <div v-if="attachment.maintainFileList.length" class="attachment-readonly-grid">
+            <div
+              v-for="f in attachment.maintainFileList"
+              :key="f.id"
+              class="attachment-readonly-item"
+              @click="handleAttachmentPreview(f)"
+            >
+              <el-image :src="f.url" fit="cover" class="attachment-readonly-img" />
+            </div>
+          </div>
+          <el-empty
+            v-else
+            description="鏆傛棤缁翠慨瀹屾垚鍥剧墖"
+            :image-size="60"
+          />
+        </div>
       </div>
       <template #footer>
         <el-button @click="attachment.visible = false">鍏抽棴</el-button>
@@ -203,7 +238,15 @@
 import {ElMessageBox, ElMessage} from "element-plus";
 import dayjs from "dayjs";
 import MaintainModal from "./Modal/MaintainModal.vue";
+import AcceptanceModal from "./Modal/AcceptanceModal.vue";
+import RepairDetailModal from "./Modal/RepairDetailModal.vue";
 import { getToken } from "@/utils/auth";
+import useUserStore from "@/store/modules/user";
+import {
+  REPAIR_FILE_TYPE_PROBLEM,
+  isProblemRepairFile,
+  isMaintainRepairFile,
+} from "@/api/equipmentManagement/repairFileType.js";
 
 defineOptions({
   name: "璁惧鎶ヤ慨",
@@ -215,6 +258,9 @@
 // 妯℃�佹瀹炰緥
 const repairModalRef = ref();
 const maintainModalRef = ref();
+const acceptanceModalRef = ref();
+const repairDetailModalRef = ref();
+const userStore = useUserStore();
 
 // 琛ㄦ牸澶氶�夋閫変腑椤�
 const multipleList = ref([]);
@@ -223,10 +269,30 @@
   visible: false,
   loading: false,
   deviceRepairId: undefined,
+  /** 鎶ヤ慨鍗曠姸鎬侊細闈炲緟缁翠慨(0)鏃堕檮浠跺脊绐楀彧璇� */
+  repairStatus: undefined,
   files: [],
-  fileList: [],
+  problemFileList: [],
+  maintainFileList: [],
   previewVisible: false,
   previewUrl: "",
+});
+
+/** 宸插紑濮嬬淮淇悗锛氬垪琛ㄣ�岄檮浠躲�嶄粎鏌ョ湅锛屼笉鍙鍒� */
+const attachmentReadOnly = computed(
+  () => attachment.repairStatus != null && attachment.repairStatus !== 0
+);
+
+const uploadDataProblem = computed(() => ({
+  deviceRepairId: attachment.deviceRepairId,
+  fileType: REPAIR_FILE_TYPE_PROBLEM,
+}));
+
+const toUploadFileItem = (item) => ({
+  id: item.id,
+  name: item.name,
+  url: getFileAccessUrl(item),
+  fileType: item.type,
 });
 
 const getFileAccessUrl = (file = {}) => {
@@ -312,6 +378,11 @@
         prop: "repairName",
       },
       {
+        label: "楠屾敹浜�",
+        align: "center",
+        prop: "acceptanceName",
+      },
+      {
         label: "鏁呴殰鐜拌薄",
         align: "center",
         prop: "remark",
@@ -345,10 +416,27 @@
         dataType: "slot",
         slot: "operation",
         align: "center",
-        width: "300px",
+        width: "420px",
       },
     ]
 );
+
+const isCurrentUser = (name) => !!name && name === userStore.nickName;
+
+const canEdit = (row) => row.status === 0;
+/** 浠呮姤淇椂鎸囧畾鐨勭淮淇汉鍙淮淇� */
+const canMaintain = (row) => {
+  if (row.status !== 0) return false;
+  if (!row.maintenanceName) return false;
+  return isCurrentUser(row.maintenanceName);
+};
+const canDelete = (row) => row.status === 0;
+/** 浠呮姤淇椂鎸囧畾鐨勯獙鏀朵汉鍙獙鏀� */
+const canAccept = (row) => {
+  if (row.status !== 3) return false;
+  if (!row.acceptanceName) return false;
+  return isCurrentUser(row.acceptanceName);
+};
 
 // type === 1 缁翠慨 2鎶ヤ慨闂�
 const handleDateChange = (value, type) => {
@@ -371,10 +459,14 @@
   multipleList.value = selectionList;
 };
 
-// 妫�鏌ラ�変腑鐨勮褰曚腑鏄惁鏈夊畬缁撶姸鎬佺殑
+// 鎵归噺鍒犻櫎锛氫粎鍏佽寰呯淮淇�
 const hasFinishedStatus = computed(() => {
-  return multipleList.value.some(item => item.status === 1)
-})
+  return multipleList.value.some((item) => item.status !== 0);
+});
+
+const openRepairDetail = (id) => {
+  repairDetailModalRef.value?.open(id);
+};
 
 // 鏂板鎶ヤ慨
 const addRepair = () => {
@@ -386,9 +478,22 @@
   repairModalRef.value.openEdit(id);
 };
 
-// 鏂板缁翠慨
+// 缁翠慨锛堜粎鎸囧畾缁翠慨浜猴級
 const addMaintain = (row) => {
+  if (!canMaintain(row)) {
+    ElMessage.warning("浠呮寚瀹氱殑缁翠慨浜哄彲杩涜缁翠慨");
+    return;
+  }
   maintainModalRef.value.open(row.id, row);
+};
+
+// 楠屾敹锛堜粎鎶ヤ慨鏃舵寚瀹氱殑楠屾敹浜恒�佷笖寰呴獙鏀剁姸鎬佸彲鐐癸級
+const openAcceptance = (row) => {
+  if (!canAccept(row)) {
+    ElMessage.warning("浠呮寚瀹氱殑楠屾敹浜哄彲杩涜楠屾敹");
+    return;
+  }
+  acceptanceModalRef.value.open(row.id, row);
 };
 
 const changePage = ({page, limit}) => {
@@ -401,13 +506,13 @@
 const delRepairByIds = async (ids) => {
   // 妫�鏌ユ槸鍚︽湁瀹岀粨鐘舵�佺殑璁板綍
   const idsArray = Array.isArray(ids) ? ids : [ids];
-  const hasFinished = idsArray.some(id => {
-    const record = dataList.value.find(item => item.id === id);
-    return record && record.status === 1;
+  const cannotDelete = idsArray.some((id) => {
+    const record = dataList.value.find((item) => item.id === id);
+    return record && record.status !== 0;
   });
 
-  if (hasFinished) {
-    ElMessage.warning('涓嶈兘鍒犻櫎鐘舵�佷负瀹岀粨鐨勮褰�');
+  if (cannotDelete) {
+    ElMessage.warning("浠呭緟缁翠慨鐘舵�佸彲鍒犻櫎");
     return;
   }
 
@@ -440,7 +545,9 @@
 };
 
 const openAttachment = async (row) => {
-  attachment.fileList = [];
+  attachment.problemFileList = [];
+  attachment.maintainFileList = [];
+  attachment.repairStatus = row?.status;
   attachment.visible = true;
   attachment.deviceRepairId = row?.id;
   await refreshAttachmentList();
@@ -452,11 +559,12 @@
   try {
     const res = await getRepairFileList(attachment.deviceRepairId);
     attachment.files = Array.isArray(res?.data) ? res.data : [];
-    attachment.fileList = attachment.files.map((item) => ({
-      id: item.id,
-      name: item.name,
-      url: getFileAccessUrl(item),
-    }));
+    attachment.problemFileList = attachment.files
+      .filter((item) => isProblemRepairFile(item.type))
+      .map(toUploadFileItem);
+    attachment.maintainFileList = attachment.files
+      .filter((item) => isMaintainRepairFile(item.type))
+      .map(toUploadFileItem);
   } finally {
     attachment.loading = false;
   }
@@ -465,13 +573,18 @@
 const onAttachmentClosed = () => {
   attachment.loading = false;
   attachment.deviceRepairId = undefined;
+  attachment.repairStatus = undefined;
   attachment.files = [];
-  attachment.fileList = [];
+  attachment.problemFileList = [];
+  attachment.maintainFileList = [];
   attachment.previewVisible = false;
   attachment.previewUrl = "";
 };
 
 const handleAttachmentBeforeUpload = (file) => {
+  if (attachmentReadOnly.value) {
+    return false;
+  }
   const isImage = ["image/png", "image/jpeg", "image/jpg"].includes(file.type);
   if (!isImage) {
     ElMessage.error("鍙兘涓婁紶 png/jpg/jpeg 鍥剧墖");
@@ -480,11 +593,9 @@
   return true;
 };
 
-const handleAttachmentUploadSuccess = async (res) => {
-  if (res?.code === 200) {
-    ElMessage.success("涓婁紶鎴愬姛");
-    await refreshAttachmentList();
-  }
+const handleAttachmentUploadSuccess = async () => {
+  ElMessage.success("涓婁紶鎴愬姛");
+  await refreshAttachmentList();
 };
 
 const handleAttachmentUploadError = () => {
@@ -501,15 +612,19 @@
   attachment.previewVisible = true;
 };
 
-const handleAttachmentRemove = async (file) => {
-  // 浠呯Щ闄ゅ墠绔湭鍏ュ簱鏂囦欢鏃讹紝涓嶈皟鐢ㄥ垹闄ゆ帴鍙�
+const handleAttachmentRemove = async (file, category) => {
+  if (attachmentReadOnly.value) {
+    return false;
+  }
   const matched = attachment.files.find((item) => item.id === file?.id)
     || attachment.files.find((item) => item.name === file?.name);
   if (!matched) return;
+  const expectProblem = category === "problem";
+  if (expectProblem && !isProblemRepairFile(matched.type)) return;
+  if (!expectProblem && !isMaintainRepairFile(matched.type)) return;
   try {
     await confirmDeleteAttachment(matched);
   } finally {
-    // 鍙栨秷鍒犻櫎鏃讹紝el-upload 宸插厛绉婚櫎锛屽埛鏂颁竴娆′繚鎸佷笌鍚庣涓�鑷�
     await refreshAttachmentList();
   }
 };
@@ -548,11 +663,43 @@
   min-height: 240px;
 }
 
-.attachment-actions {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
+.attachment-section {
+  margin-bottom: 20px;
+}
+.attachment-section-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: #303133;
   margin-bottom: 12px;
+  padding-left: 8px;
+  border-left: 3px solid var(--el-color-primary);
+}
+
+.attachment-readonly-tip {
+  font-size: 13px;
+  color: var(--el-text-color-secondary);
+  margin-bottom: 16px;
+  line-height: 1.5;
+}
+
+.attachment-readonly-grid {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+}
+
+.attachment-readonly-item {
+  width: 100px;
+  height: 100px;
+  border-radius: 6px;
+  overflow: hidden;
+  cursor: pointer;
+  border: 1px solid var(--el-border-color);
+}
+
+.attachment-readonly-img {
+  width: 100%;
+  height: 100%;
 }
 
 .attachment-preview-wrap {

--
Gitblit v1.9.3