From 1cfe07dc5c8f98102e9a428b580fd2a81761ad0e Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期一, 30 三月 2026 11:54:22 +0800
Subject: [PATCH] 酒泉 1.设备功能迁移

---
 src/views/equipmentManagement/upkeep/Form/ApproveModal.vue                  |  144 +
 src/views/equipmentManagement/upkeep/index.vue                              |  878 ++++++++---
 src/views/equipmentManagement/spareParts/index.vue                          |  478 +++--
 src/views/equipmentManagement/repair/Modal/MaintainModal.vue                |  127 +
 src/views/equipmentManagement/upkeep/Form/PlanModal.vue                     |  188 ++
 src/views/equipmentManagement/repair/Modal/RepairModal.vue                  |  232 ++
 src/views/equipmentManagement/inspectionManagement/components/formDia.vue   |   39 
 src/views/equipmentManagement/repair/Modal/ApproveModal.vue                 |  142 +
 src/views/equipmentManagement/defectManagement/index.vue                    |    2 
 src/views/equipmentManagement/repair/index.vue                              |  265 ++-
 src/api/equipmentManagement/upkeep.js                                       |   49 
 src/views/equipmentManagement/inspectionManagement/index.vue                |   50 
 src/views/equipmentManagement/ledger/Form.vue                               |  118 -
 src/api/equipmentManagement/maintenanceTaskFile.js                          |   28 
 src/components/Dialog/ImportDialog.vue                                      |  172 ++
 src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue |   74 
 /dev/null                                                                   |   76 -
 src/api/equipmentManagement/repair.js                                       |   16 
 src/views/equipmentManagement/amountSummary/index.vue                       |  180 ++
 src/views/equipmentManagement/upkeep/Form/formDia.vue                       |  337 ++++
 src/views/equipmentManagement/ledger/index.vue                              |  225 ++
 src/components/Dialog/FileListDialog.vue                                    |  328 ++++
 src/components/Dialog/FormDialog.vue                                        |   73 +
 src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue              |  146 ++
 24 files changed, 3,454 insertions(+), 913 deletions(-)

diff --git a/src/api/equipmentManagement/maintenanceTaskFile.js b/src/api/equipmentManagement/maintenanceTaskFile.js
new file mode 100644
index 0000000..8373ae3
--- /dev/null
+++ b/src/api/equipmentManagement/maintenanceTaskFile.js
@@ -0,0 +1,28 @@
+import request from "@/utils/request";
+
+// 鏌ヨ淇濆吇浠诲姟闄勪欢鍒楄〃
+export function listMaintenanceTaskFiles(query) {
+  return request({
+    url: "/maintenanceTaskFile/listPage",
+    method: "get",
+    params: query,
+  });
+}
+
+// 鏂板淇濆吇浠诲姟闄勪欢
+export function addMaintenanceTaskFile(data) {
+  return request({
+    url: "/maintenanceTaskFile/add",
+    method: "post",
+    data,
+  });
+}
+
+// 鍒犻櫎淇濆吇浠诲姟闄勪欢
+export function delMaintenanceTaskFile(id) {
+  return request({
+    url: "/maintenanceTaskFile/del",
+    method: "delete",
+    data: Array.isArray(id) ? id : [id],
+  });
+}
diff --git a/src/api/equipmentManagement/repair.js b/src/api/equipmentManagement/repair.js
index 0233ae6..bb44f8e 100644
--- a/src/api/equipmentManagement/repair.js
+++ b/src/api/equipmentManagement/repair.js
@@ -70,3 +70,19 @@
     data,
   });
 };
+// 鏌ヨ璁惧鐨勬姤淇噾棰�-姣忔湀
+export const monthlyAmount = (query) => {
+  return request({
+    url: `/device/repair/monthlyAmount`,
+    method: "get",
+    params: query,
+  });
+};
+// 鏌ヨ璁惧鐨勬姤淇噾棰�-姣忓勾
+export const yearlyAmount = (query) => {
+  return request({
+    url: `/device/repair/yearlyAmount`,
+    method: "get",
+    params: query,
+  });
+};
diff --git a/src/api/equipmentManagement/upkeep.js b/src/api/equipmentManagement/upkeep.js
index c091670..37f6e5f 100644
--- a/src/api/equipmentManagement/upkeep.js
+++ b/src/api/equipmentManagement/upkeep.js
@@ -70,3 +70,52 @@
     method: "delete",
   });
 };
+// 娣诲姞璁惧淇濆吇瀹氭椂浠诲姟
+export const deviceMaintenanceTaskAdd = (params) => {
+  return request({
+    url: '/deviceMaintenanceTask/add',
+    method: "post",
+    data: params,
+  });
+};
+// 淇敼璁惧淇濆吇瀹氭椂浠诲姟
+export const deviceMaintenanceTaskEdit = (params) => {
+  return request({
+    url: '/deviceMaintenanceTask/update',
+    method: "post",
+    data: params,
+  });
+};
+// 璁惧淇濆吇瀹氭椂浠诲姟鍒楄〃
+export const deviceMaintenanceTaskList = (params) => {
+  return request({
+    url: '/deviceMaintenanceTask/listPage',
+    method: "get",
+    params: params,
+  });
+};
+// 璁惧淇濆吇瀹氭椂浠诲姟鍒楄〃
+export const deviceMaintenanceTaskDel = (params) => {
+  return request({
+    url: '/deviceMaintenanceTask/delete',
+    method: "delete",
+    data: params,
+  });
+};
+
+// 鏌ヨ璁惧鐨勬姤淇噾棰�-姣忔湀
+export const monthlyAmount = (query) => {
+  return request({
+    url: `/device/maintenance/monthlyAmount`,
+    method: "get",
+    params: query,
+  });
+};
+// 鏌ヨ璁惧鐨勬姤淇噾棰�-姣忓勾
+export const yearlyAmount = (query) => {
+  return request({
+    url: `/device/maintenance/yearlyAmount`,
+    method: "get",
+    params: query,
+  });
+};
diff --git a/src/components/Dialog/FileListDialog.vue b/src/components/Dialog/FileListDialog.vue
new file mode 100644
index 0000000..fc82411
--- /dev/null
+++ b/src/components/Dialog/FileListDialog.vue
@@ -0,0 +1,328 @@
+<template>
+  <el-dialog v-model="dialogVisible"
+             :title="title"
+             :width="width"
+             :before-close="handleClose">
+    <div class="file-list-toolbar"
+         v-if="showToolbar">
+      <template v-if="useBuiltInUpload">
+        <el-upload v-model:file-list="uploadFileList"
+                   class="upload-demo"
+                   :action="uploadAction"
+                   :headers="uploadHeaders"
+                   :show-file-list="false"
+                   :on-success="handleDefaultUploadSuccess"
+                   :on-error="handleDefaultUploadError">
+          <el-button v-if="showUploadButton"
+                     type="primary"
+                     size="small">
+            涓婁紶闄勪欢
+          </el-button>
+        </el-upload>
+      </template>
+      <template v-else>
+        <el-button v-if="showUploadButton"
+                   type="primary"
+                   size="small"
+                   @click="handleUpload">
+          鏂板闄勪欢
+        </el-button>
+      </template>
+    </div>
+    <el-table :data="tableData"
+              border
+              :height="tableHeight">
+      <el-table-column :label="nameColumnLabel"
+                       :prop="nameColumnProp"
+                       :min-width="nameColumnMinWidth"
+                       show-overflow-tooltip />
+      <el-table-column v-if="showActions"
+                       fixed="right"
+                       label="鎿嶄綔"
+                       :width="actionColumnWidth"
+                       align="center">
+        <template #default="scope">
+          <el-button v-if="showDownload"
+                     link
+                     type="primary"
+                     size="small"
+                     @click="handleDownload(scope.row)">
+            涓嬭浇
+          </el-button>
+          <el-button v-if="showPreview"
+                     link
+                     type="primary"
+                     size="small"
+                     @click="handlePreview(scope.row)">
+            棰勮
+          </el-button>
+          <el-button v-if="showDeleteButton"
+                     link
+                     type="danger"
+                     size="small"
+                     @click="handleDelete(scope.row, scope.$index)">
+            鍒犻櫎
+          </el-button>
+          <slot name="actions"
+                :row="scope.row"></slot>
+        </template>
+      </el-table-column>
+      <slot name="columns"></slot>
+    </el-table>
+    <pagination v-if="isShowPagination"
+                style="margin-bottom: 20px;"
+                :total="page.total"
+                :page="page.current"
+                :limit="page.size"
+                @pagination="paginationSearch"
+                @change="handleChange" />
+  </el-dialog>
+  <filePreview v-if="showPreview"
+               ref="filePreviewRef" />
+</template>
+
+<script setup>
+  import { ref, computed, getCurrentInstance } from "vue";
+  import pagination from "@/components/Pagination/index.vue";
+  import { ElMessage } from "element-plus";
+  import filePreview from "@/components/filePreview/index.vue";
+  import { getToken } from "@/utils/auth";
+
+  const props = defineProps({
+    modelValue: {
+      type: Boolean,
+      default: false,
+    },
+    title: {
+      type: String,
+      default: "闄勪欢",
+    },
+    width: {
+      type: String,
+      default: "40%",
+    },
+    tableHeight: {
+      type: String,
+      default: "40vh",
+    },
+    nameColumnLabel: {
+      type: String,
+      default: "闄勪欢鍚嶇О",
+    },
+    nameColumnProp: {
+      type: String,
+      default: "name",
+    },
+    nameColumnMinWidth: {
+      type: [String, Number],
+      default: 400,
+    },
+    actionColumnWidth: {
+      type: [String, Number],
+      default: 160,
+    },
+    showActions: {
+      type: Boolean,
+      default: true,
+    },
+    showDownload: {
+      type: Boolean,
+      default: true,
+    },
+    showPreview: {
+      type: Boolean,
+      default: true,
+    },
+    showUploadButton: {
+      type: Boolean,
+      default: false,
+    },
+    showDeleteButton: {
+      type: Boolean,
+      default: false,
+    },
+    urlField: {
+      type: String,
+      default: "url",
+    },
+    downloadMethod: {
+      type: Function,
+      default: null,
+    },
+    previewMethod: {
+      type: Function,
+      default: null,
+    },
+    uploadMethod: {
+      type: Function,
+      default: null,
+    },
+    deleteMethod: {
+      type: Function,
+      default: null,
+    },
+    rulesRegulationsManagementId: {
+      type: [String, Number],
+      default: "",
+    },
+    uploadUrl: {
+      type: String,
+      default: `${import.meta.env.VITE_APP_BASE_API}/file/upload`,
+    },
+    isShowPagination: {
+      type: Boolean,
+      default: false,
+    },
+    page: {
+      type: Object,
+      default: () => ({
+        current: 1,
+        size: 10,
+        total: 0,
+      }),
+    },
+  });
+
+  const emit = defineEmits([
+    "update:modelValue",
+    "close",
+    "download",
+    "preview",
+    "upload",
+    "delete",
+  ]);
+
+  const { proxy } = getCurrentInstance();
+  const filePreviewRef = ref(null);
+  const uploadFileList = ref([]);
+
+  const dialogVisible = computed({
+    get: () => props.modelValue,
+    set: val => emit("update:modelValue", val),
+  });
+
+  const tableData = ref([]);
+  const showToolbar = computed(() => props.showUploadButton);
+  const useBuiltInUpload = computed(() => !props.uploadMethod);
+  const uploadAction = computed(() => props.uploadUrl);
+  const uploadHeaders = computed(() => ({
+    Authorization: `Bearer ${getToken()}`,
+  }));
+
+  const handleClose = () => {
+    emit("close");
+    dialogVisible.value = false;
+  };
+
+  const handleDownload = row => {
+    if (props.downloadMethod) {
+      props.downloadMethod(row);
+    } else {
+      // 榛樿涓嬭浇鏂规硶
+      proxy.$download.name(row[props.urlField]);
+    }
+    emit("download", row);
+  };
+
+  const handlePreview = row => {
+    if (props.previewMethod) {
+      props.previewMethod(row);
+    } else {
+      // 榛樿棰勮鏂规硶
+      if (filePreviewRef.value) {
+        filePreviewRef.value.open(row[props.urlField]);
+      }
+    }
+    emit("preview", row);
+  };
+  const paginationSearch = page => {
+    props.page.current = page.page;
+    props.page.size = page.limit;
+    emit("pagination", page.page, page.limit);
+  };
+
+  const open = list => {
+    dialogVisible.value = true;
+    tableData.value = list || [];
+  };
+
+  const handleUpload = async () => {
+    if (props.uploadMethod) {
+      // 濡傛灉鎻愪緵浜嗚嚜瀹氫箟涓婁紶鏂规硶锛岀敱鐖剁粍浠惰礋璐f洿鏂板垪琛紙閫氳繃 setList锛�
+      // 杩欓噷涓嶅啀鑷姩娣诲姞锛岄伩鍏嶄笌鐖剁粍浠剁殑 setList 閲嶅
+      await props.uploadMethod();
+    }
+    emit("upload");
+  };
+
+  const handleDelete = async (row, index) => {
+    if (props.deleteMethod) {
+      const result = await props.deleteMethod(row, index);
+      if (result === false) {
+        return;
+      }
+      // 濡傛灉鎻愪緵浜� deleteMethod锛岀敱鐖剁粍浠惰礋璐e埛鏂板垪琛紝涓嶅湪杩欓噷鍒犻櫎
+    } else {
+      // 濡傛灉娌℃湁鎻愪緵 deleteMethod锛屾墠鍦ㄧ粍浠跺唴閮ㄥ垹闄�
+      removeAttachment(index);
+    }
+    emit("delete", row);
+  };
+
+  const addAttachment = item => {
+    tableData.value = [...tableData.value, item];
+  };
+
+  const handleDefaultUploadSuccess = async (res, file) => {
+    if (res?.code !== 200) {
+      ElMessage.error(res?.msg || "鏂囦欢涓婁紶澶辫触");
+      return;
+    }
+    if (!props.rulesRegulationsManagementId) {
+      ElMessage.error("缂哄皯瑙勭珷鍒跺害ID锛屾棤娉曚繚瀛橀檮浠�");
+      return;
+    }
+    const fileName = res?.data?.originalName || file?.name;
+    const fileUrl = res?.data?.tempPath || res?.data?.url;
+    const payload = {
+      fileName,
+      fileUrl,
+      rulesRegulationsManagementId: props.rulesRegulationsManagementId,
+      raw: res?.data || {},
+    };
+    emit("upload", payload);
+  };
+
+  const handleDefaultUploadError = () => {
+    ElMessage.error("鏂囦欢涓婁紶澶辫触");
+  };
+
+  const removeAttachment = index => {
+    if (index > -1 && index < tableData.value.length) {
+      const newList = [...tableData.value];
+      newList.splice(index, 1);
+      tableData.value = newList;
+    }
+  };
+
+  const setList = list => {
+    tableData.value = list || [];
+  };
+
+  defineExpose({
+    open,
+    addAttachment,
+    removeAttachment,
+    setList,
+    handleUpload,
+    handleDelete,
+  });
+</script>
+
+<style scoped>
+  .file-list-toolbar {
+    margin-bottom: 8px;
+    text-align: right;
+  }
+</style>
+
diff --git a/src/components/Dialog/FormDialog.vue b/src/components/Dialog/FormDialog.vue
new file mode 100644
index 0000000..5e21b1d
--- /dev/null
+++ b/src/components/Dialog/FormDialog.vue
@@ -0,0 +1,73 @@
+<template>
+  <el-dialog
+    v-model="dialogVisible"
+    :title="computedTitle"
+    :width="width"
+    @close="handleClose"
+  >
+    <slot></slot>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button type="primary" @click="handleConfirm">纭</el-button>
+        <el-button @click="handleCancel">鍙栨秷</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { computed } from 'vue'
+
+const props = defineProps({
+  modelValue: {
+    type: Boolean,
+    default: false
+  },
+  title: {
+    type: [String, Function],
+    default: ''
+  },
+  operationType: {
+    type: String,
+    default: ''
+  },
+  width: {
+    type: String,
+    default: '70%'
+  }
+})
+
+const emit = defineEmits(['update:modelValue', 'close', 'confirm', 'cancel'])
+
+const dialogVisible = computed({
+  get: () => props.modelValue,
+  set: (val) => emit('update:modelValue', val)
+})
+
+const computedTitle = computed(() => {
+  if (typeof props.title === 'function') {
+    return props.title(props.operationType)
+  }
+  return props.title
+})
+
+const handleClose = () => {
+  emit('close')
+}
+
+const handleConfirm = () => {
+  emit('confirm')
+}
+
+const handleCancel = () => {
+  emit('cancel')
+  dialogVisible.value = false
+}
+</script>
+
+<style scoped>
+.dialog-footer {
+  text-align: center;
+}
+</style>
+
diff --git a/src/components/Dialog/ImportDialog.vue b/src/components/Dialog/ImportDialog.vue
new file mode 100644
index 0000000..5b126dc
--- /dev/null
+++ b/src/components/Dialog/ImportDialog.vue
@@ -0,0 +1,172 @@
+<template>
+  <el-dialog
+    :title="title"
+    v-model="dialogVisible"
+    :width="width"
+    :append-to-body="appendToBody"
+    @close="handleClose"
+  >
+    <el-upload
+      ref="uploadRef"
+      :limit="limit"
+      :accept="accept"
+      :headers="headers"
+      :action="action"
+      :disabled="disabled"
+      :before-upload="beforeUpload"
+      :on-progress="onProgress"
+      :on-success="onSuccess"
+      :on-error="onError"
+      :on-change="onChange"
+      :auto-upload="autoUpload"
+      drag
+    >
+      <el-icon class="el-icon--upload"><UploadFilled /></el-icon>
+      <div class="el-upload__text">灏嗘枃浠舵嫋鍒版澶勶紝鎴�<em>鐐瑰嚮涓婁紶</em></div>
+      <template #tip>
+        <div class="el-upload__tip text-center">
+          <span>{{ tipText }}</span>
+          <el-link
+            v-if="showDownloadTemplate"
+            type="primary"
+            :underline="false"
+            style="font-size: 12px; vertical-align: baseline; margin-left: 5px;"
+            @click="handleDownloadTemplate"
+            >涓嬭浇妯℃澘</el-link
+          >
+        </div>
+      </template>
+    </el-upload>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button type="primary" @click="handleConfirm">纭� 瀹�</el-button>
+        <el-button @click="handleCancel">鍙� 娑�</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { computed, ref } from 'vue'
+import { UploadFilled } from '@element-plus/icons-vue'
+
+const props = defineProps({
+  modelValue: {
+    type: Boolean,
+    default: false
+  },
+  title: {
+    type: String,
+    default: '瀵煎叆'
+  },
+  width: {
+    type: String,
+    default: '400px'
+  },
+  appendToBody: {
+    type: Boolean,
+    default: true
+  },
+  limit: {
+    type: Number,
+    default: 1
+  },
+  accept: {
+    type: String,
+    default: '.xlsx, .xls'
+  },
+  headers: {
+    type: Object,
+    default: () => ({})
+  },
+  action: {
+    type: String,
+    required: true
+  },
+  disabled: {
+    type: Boolean,
+    default: false
+  },
+  autoUpload: {
+    type: Boolean,
+    default: false
+  },
+  tipText: {
+    type: String,
+    default: '浠呭厑璁稿鍏ls銆亁lsx鏍煎紡鏂囦欢銆�'
+  },
+  showDownloadTemplate: {
+    type: Boolean,
+    default: true
+  },
+  beforeUpload: {
+    type: Function,
+    default: null
+  },
+  onProgress: {
+    type: Function,
+    default: null
+  },
+  onSuccess: {
+    type: Function,
+    default: null
+  },
+  onError: {
+    type: Function,
+    default: null
+  },
+  onChange: {
+    type: Function,
+    default: null
+  }
+})
+
+const emit = defineEmits(['update:modelValue', 'close', 'confirm', 'cancel', 'download-template'])
+
+const dialogVisible = computed({
+  get: () => props.modelValue,
+  set: (val) => emit('update:modelValue', val)
+})
+
+const uploadRef = ref(null)
+
+const handleClose = () => {
+  emit('close')
+}
+
+const handleConfirm = () => {
+  emit('confirm')
+}
+
+const submit = () => {
+  if (uploadRef.value) {
+    uploadRef.value.submit()
+  }
+}
+
+const handleCancel = () => {
+  emit('cancel')
+  dialogVisible.value = false
+}
+
+const handleDownloadTemplate = () => {
+  emit('download-template')
+}
+
+defineExpose({
+  uploadRef,
+  submit,
+  clearFiles: () => {
+    if (uploadRef.value) {
+      uploadRef.value.clearFiles()
+    }
+  }
+})
+</script>
+
+<style scoped>
+.dialog-footer {
+  text-align: center;
+}
+</style>
+
diff --git a/src/views/equipmentManagement/amountSummary/index.vue b/src/views/equipmentManagement/amountSummary/index.vue
new file mode 100644
index 0000000..9d6a8bc
--- /dev/null
+++ b/src/views/equipmentManagement/amountSummary/index.vue
@@ -0,0 +1,180 @@
+<template>
+  <div class="app-container">
+    <el-tabs v-model="activeTab">
+      <el-tab-pane label="鎶ヤ慨" name="repair">
+        <el-form :inline="true" :model="filtersRepair">
+          <el-form-item label="鏌ョ湅妯″紡">
+            <el-radio-group v-model="modeRepair" @change="buildColumnsRepair">
+              <el-radio-button :value="'month'">鎸夋湀</el-radio-button>
+              <el-radio-button :value="'year'">鎸夊勾</el-radio-button>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item v-if="modeRepair === 'month' || modeRepair === 'year'" label="骞翠唤">
+            <el-date-picker v-model="yearRepair" type="year" value-format="YYYY" format="YYYY" placeholder="璇烽�夋嫨骞翠唤" @change="refreshRepair" />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="refreshRepair">鏌ヨ</el-button>
+            <el-button @click="resetRepair">閲嶇疆</el-button>
+          </el-form-item>
+        </el-form>
+
+        <div class="table_list">
+          <PIMTable
+            rowKey="deviceId"
+            :column="columnsRepair"
+            :tableData="tableDataRepair"
+            :page="{ current: 1, size: tableDataRepair.length || 10, total: tableDataRepair.length }"
+          />
+        </div>
+      </el-tab-pane>
+
+      <el-tab-pane label="淇濆吇" name="maintain">
+        <el-form :inline="true" :model="filtersMaintain">
+          <el-form-item label="鏌ョ湅妯″紡">
+            <el-radio-group v-model="modeMaintain" @change="buildColumnsMaintain">
+              <el-radio-button :value="'month'">鎸夋湀</el-radio-button>
+              <el-radio-button :value="'year'">鎸夊勾</el-radio-button>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item v-if="modeMaintain === 'month' || modeMaintain === 'year'" label="骞翠唤">
+            <el-date-picker v-model="yearMaintain" type="year" value-format="YYYY" format="YYYY" placeholder="璇烽�夋嫨骞翠唤" @change="refreshMaintain" />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="refreshMaintain">鏌ヨ</el-button>
+            <el-button @click="resetMaintain">閲嶇疆</el-button>
+          </el-form-item>
+        </el-form>
+
+        <div class="table_list">
+          <PIMTable
+            rowKey="deviceId"
+            :column="columnsMaintain"
+            :tableData="tableDataMaintain"
+            :page="{ current: 1, size: tableDataMaintain.length || 10, total: tableDataMaintain.length }"
+          />
+        </div>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+  </template>
+
+<script setup>
+import { ref } from "vue";
+import { monthlyAmount, yearlyAmount } from "@/api/equipmentManagement/repair";
+import { monthlyAmount as monthlyAmountMaintain, yearlyAmount as yearlyAmountMaintain } from "@/api/equipmentManagement/upkeep";
+
+defineOptions({ name: "閲戦姹囨��" });
+
+const activeTab = ref("repair");
+
+// 鎶ヤ慨
+const modeRepair = ref("month");
+const yearRepair = ref(new Date().getFullYear().toString());
+const filtersRepair = ref({});
+const columnsRepair = ref([]);
+const tableDataRepair = ref([]);
+
+const fetchRepairData = async () => {
+  try {
+    const query = { year: yearRepair.value };
+    const res = modeRepair.value === "month" ? await monthlyAmount(query) : await yearlyAmount(query);
+    const list = res?.records || res?.data || res || [];
+    tableDataRepair.value = Array.isArray(list) ? list : [];
+  } catch (e) {
+    tableDataRepair.value = [];
+  }
+};
+
+const buildColumnsRepair = async () => {
+  const base = [{ label: "璁惧鍚嶇О", align: "center", prop: "deviceName", width: 180 }];
+  if (modeRepair.value === "month") {
+    const monthCols = Array.from({ length: 12 }, (_, i) => ({
+      label: `${i + 1}鏈坄,
+      align: "center",
+      prop: `month${i + 1}`,
+    }));
+    columnsRepair.value = [
+      ...base,
+      ...monthCols,
+      { label: "鎬昏", align: "center", prop: "total" },
+    ];
+  } else {
+    columnsRepair.value = [
+      ...base,
+      { label: "閲戦", align: "center", prop: "totalRepairPrice" },
+    ];
+  }
+  await fetchRepairData();
+};
+
+const refreshRepair = async () => {
+  await buildColumnsRepair();
+};
+
+const resetRepair = () => {
+  modeRepair.value = "month";
+  yearRepair.value = new Date().getFullYear().toString();
+  refreshRepair();
+};
+
+// 淇濆吇
+const modeMaintain = ref("month");
+const yearMaintain = ref(new Date().getFullYear().toString());
+const filtersMaintain = ref({});
+const columnsMaintain = ref([]);
+const tableDataMaintain = ref([]);
+
+const fetchMaintainData = async () => {
+  try {
+    const query = { year: yearMaintain.value };
+    const res = modeMaintain.value === "month" ? await monthlyAmountMaintain(query) : await yearlyAmountMaintain(query);
+    const list = res?.records || res?.data || res || [];
+    tableDataMaintain.value = Array.isArray(list) ? list : [];
+  } catch (e) {
+    tableDataMaintain.value = [];
+  }
+};
+
+const buildColumnsMaintain = async () => {
+  const base = [{ label: "璁惧鍚嶇О", align: "center", prop: "deviceName", width: 180 }];
+  if (modeMaintain.value === "month") {
+    const monthCols = Array.from({ length: 12 }, (_, i) => ({
+      label: `${i + 1}鏈坄,
+      align: "center",
+      prop: `month${i + 1}`,
+    }));
+    columnsMaintain.value = [
+      ...base,
+      ...monthCols,
+      { label: "鎬昏", align: "center", prop: "total" },
+    ];
+  } else {
+    columnsMaintain.value = [
+      ...base,
+      { label: "閲戦", align: "center", prop: "totalRepairPrice" },
+    ];
+  }
+  await fetchMaintainData();
+};
+
+const refreshMaintain = async () => {
+  await buildColumnsMaintain();
+};
+
+const resetMaintain = () => {
+  modeMaintain.value = "month";
+  yearMaintain.value = new Date().getFullYear().toString();
+  refreshMaintain();
+};
+
+buildColumnsRepair();
+refreshRepair();
+buildColumnsMaintain();
+refreshMaintain();
+</script>
+
+<style scoped>
+.table_list {
+  margin-top: 10px;
+}
+</style>
diff --git a/src/views/equipmentManagement/defectManagement/index.vue b/src/views/equipmentManagement/defectManagement/index.vue
index f35454f..139f139 100644
--- a/src/views/equipmentManagement/defectManagement/index.vue
+++ b/src/views/equipmentManagement/defectManagement/index.vue
@@ -43,7 +43,7 @@
     <el-dialog title="鐧昏璁惧缂洪櫡" v-model="showRegisterDialog" width="50%">
       <el-form :model="defectForm" :rules="defectRules" ref="defectFormRef" label-width="100px">
         <el-form-item label="璁惧鍚嶇О" prop="deviceName">
-          <el-select v-model="defectForm.deviceLedgerId" @change="setDeviceModel">
+          <el-select v-model="defectForm.deviceLedgerId" @change="setDeviceModel" filterable>
             <el-option
               v-for="(item, index) in deviceOptions"
               :key="index"
diff --git a/src/views/equipmentManagement/inspectionManagement/components/formDia.vue b/src/views/equipmentManagement/inspectionManagement/components/formDia.vue
index 79ff5b0..87c9cee 100644
--- a/src/views/equipmentManagement/inspectionManagement/components/formDia.vue
+++ b/src/views/equipmentManagement/inspectionManagement/components/formDia.vue
@@ -5,8 +5,8 @@
       <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
         <el-row>
           <el-col :span="12">
-            <el-form-item label="璁惧鍚嶇О" prop="taskId">
-              <el-select v-model="form.taskId" @change="setDeviceModel">
+            <el-form-item label="璁惧鍚嶇О" prop="taskIds">
+              <el-select v-model="form.taskIds" @change="setDeviceModel" multiple filterable>
                 <el-option
                   v-for="(item, index) in deviceOptions"
                   :key="index"
@@ -91,8 +91,8 @@
       </el-form>
       <template #footer>
         <div class="dialog-footer">
-          <el-button @click="cancel">鍙栨秷</el-button>
           <el-button type="primary" @click="submitForm">淇濆瓨</el-button>
+          <el-button @click="cancel">鍙栨秷</el-button>
         </div>
       </template>
     </el-dialog>
@@ -114,7 +114,7 @@
 const deviceOptions = ref([]);
 const data = reactive({
   form: {
-    taskId: undefined,
+    taskIds: [],
     taskName: undefined,
     inspector: '',
     inspectorIds: '',
@@ -125,7 +125,7 @@
     time: ''
   },
   rules: {
-    taskId: [{ required: true, message: "璇烽�夋嫨璁惧", trigger: "change" },],
+    taskIds: [{ required: true, message: "璇烽�夋嫨璁惧", trigger: "change" },],
     inspector: [{ required: true, message: "璇疯緭鍏ュ贰妫�浜�", trigger: "blur" },],
   }
 })
@@ -137,10 +137,17 @@
   deviceOptions.value = data;
 };
 
-const setDeviceModel = (id) => {
-  const option = deviceOptions.value.find((item) => item.id === id);
-  if (option) {
-    form.value.taskName = option.deviceName;
+const setDeviceModel = (ids) => {
+  if (!ids || ids.length === 0) {
+    form.value.taskIds = []
+    form.value.taskName = undefined
+    return
+  }
+  
+  const selectedDevices = deviceOptions.value.filter((item) => ids.includes(item.id))
+  if (selectedDevices.length > 0) {
+    form.value.taskIds = ids
+    form.value.taskName = selectedDevices.map(d => d.deviceName).join(',')
   }
 }
 
@@ -164,9 +171,10 @@
     form.value = {...row}
     form.value.inspector = form.value.inspectorIds.split(',').map(Number)
     
-    // 濡傛灉鏈夎澶嘔D锛岃嚜鍔ㄨ缃澶囦俊鎭�
-    if (form.value.taskId) {
-      setDeviceModel(form.value.taskId);
+    // 濡傛灉鏈夎澶嘔D鏁扮粍锛岃浆鎹负鏁扮粍骞惰缃澶囦俊鎭�
+    if (row.taskIds) {
+      form.value.taskIds = row.taskIds.split(',').map(id => parseInt(id.trim()))
+      setDeviceModel(form.value.taskIds)
     }
   }
 }
@@ -185,7 +193,7 @@
   }
   // 閲嶇疆琛ㄥ崟鏁版嵁纭繚璁惧淇℃伅姝g‘閲嶇疆
   form.value = {
-    taskId: undefined,
+    taskIds: [],
     taskName: undefined,
     inspector: '',
     inspectorIds: '',
@@ -205,6 +213,11 @@
         form.value.inspectorIds = form.value.inspector.join(',')
         delete form.value.inspector
         
+        // 澶勭悊 taskIds 鍜� taskName
+        if (form.value.taskIds && Array.isArray(form.value.taskIds)) {
+          form.value.taskIds = form.value.taskIds.join(',')
+        }
+        
         if (form.value.frequencyType === 'WEEKLY') {
           let frequencyDetail = ''
           frequencyDetail = form.value.week + ',' + form.value.time
diff --git a/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue b/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
index d1c9d8d..27b4a59 100644
--- a/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
+++ b/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
@@ -100,7 +100,7 @@
         
         <!-- 瑙嗛 -->
         <div v-else-if="mediaType === 'video'" style="position: relative;">
-          <Video
+          <video
               :src="mediaList[currentMediaIndex]"
               autoplay
               controls
@@ -114,6 +114,7 @@
 <script setup>
 import { ref } from 'vue';
 import VueEasyLightbox from 'vue-easy-lightbox';
+const { proxy } = getCurrentInstance();
 
 // 鎺у埗寮圭獥鏄剧ず
 const dialogVisitable = ref(false);
@@ -133,26 +134,83 @@
 const currentMediaIndex = ref(0);
 const mediaList = ref([]); // 瀛樺偍褰撳墠瑕佹煡鐪嬬殑濯掍綋鍒楄〃锛堝惈鍥剧墖鍜岃棰戝璞★級
 const mediaType = ref('image'); // image | video
+const javaApi = proxy.javaApi;
+
+// 澶勭悊 URL锛氬皢 Windows 璺緞杞崲涓哄彲璁块棶鐨� URL
+function processFileUrl(fileUrl) {
+  if (!fileUrl) return '';
+  
+  // 濡傛灉 URL 鏄� Windows 璺緞鏍煎紡锛堝寘鍚弽鏂滄潬锛夛紝闇�瑕佽浆鎹�
+  if (fileUrl && fileUrl.indexOf('\\') > -1) {
+    // 鏌ユ壘 uploads 鍏抽敭瀛楃殑浣嶇疆锛屼粠閭i噷寮�濮嬫彁鍙栫浉瀵硅矾寰�
+    const uploadsIndex = fileUrl.toLowerCase().indexOf('uploads');
+    if (uploadsIndex > -1) {
+      // 浠� uploads 寮�濮嬫彁鍙栬矾寰勶紝骞跺皢鍙嶆枩鏉犳浛鎹负姝f枩鏉�
+      const relativePath = fileUrl.substring(uploadsIndex).replace(/\\/g, '/');
+      fileUrl = '/' + relativePath;
+    } else {
+      // 濡傛灉娌℃湁鎵惧埌 uploads锛屾彁鍙栨渶鍚庝竴涓洰褰曞拰鏂囦欢鍚�
+      const parts = fileUrl.split('\\');
+      const fileName = parts[parts.length - 1];
+      fileUrl = '/uploads/' + fileName;
+    }
+  }
+  
+  // 纭繚鎵�鏈夐潪 http 寮�澶寸殑 URL 閮芥嫾鎺� baseUrl
+  if (fileUrl && !fileUrl.startsWith('http')) {
+    // 纭繚璺緞浠� / 寮�澶�
+    if (!fileUrl.startsWith('/')) {
+      fileUrl = '/' + fileUrl;
+    }
+    // 鎷兼帴 baseUrl
+    fileUrl = javaApi + fileUrl;
+  }
+  
+  return fileUrl;
+}
 
 // 澶勭悊姣忎竴绫绘暟鎹細鍒嗙鍥剧墖鍜岃棰�
 function processItems(items) {
   const images = [];
   const videos = [];
+  
+  // 妫�鏌� items 鏄惁瀛樺湪涓斾负鏁扮粍
+  if (!items || !Array.isArray(items)) {
+    return { images, videos };
+  }
+  
   items.forEach(item => {
-    if (item.contentType?.startsWith('image/')) {
-      images.push(item.url);
-    } else if (item.contentType?.startsWith('video/')) {
-      videos.push(item.url);
+    if (!item || !item.url) return;
+    
+    // 澶勭悊鏂囦欢 URL
+    const fileUrl = processFileUrl(item.url);
+    
+    // 鏍规嵁鏂囦欢鎵╁睍鍚嶅垽鏂槸鍥剧墖杩樻槸瑙嗛
+    const urlLower = fileUrl.toLowerCase();
+    if (urlLower.match(/\.(jpg|jpeg|png|gif|bmp|webp)$/)) {
+      images.push(fileUrl);
+    } else if (urlLower.match(/\.(mp4|avi|mov|wmv|flv|mkv|webm)$/)) {
+      videos.push(fileUrl);
+    } else if (item.contentType) {
+      // 濡傛灉鏈� contentType锛屼娇鐢� contentType 鍒ゆ柇
+      if (item.contentType.startsWith('image/')) {
+        images.push(fileUrl);
+      } else if (item.contentType.startsWith('video/')) {
+        videos.push(fileUrl);
+      }
     }
   });
+  
   return { images, videos };
 }
 
 // 鎵撳紑寮圭獥骞跺姞杞芥暟鎹�
 const openDialog = async (row) => {
-  const { images: beforeImgs, videos: beforeVids } = processItems(row.beforeProduction);
-  const { images: afterImgs, videos: afterVids } = processItems(row.afterProduction);
-  const { images: issueImgs, videos: issueVids } = processItems(row.productionIssues);
+  // 浣跨敤姝g‘鐨勫瓧娈靛悕锛歝ommonFileListBefore, commonFileListAfter
+  // productionIssues 鍙兘涓嶅瓨鍦紝浣跨敤绌烘暟缁�
+  const { images: beforeImgs, videos: beforeVids } = processItems(row.commonFileListBefore || []);
+  const { images: afterImgs, videos: afterVids } = processItems(row.commonFileListAfter || []);
+  const { images: issueImgs, videos: issueVids } = processItems(row.productionIssues || []);
   
   beforeProductionImgs.value = beforeImgs;
   beforeProductionVideos.value = beforeVids;
diff --git a/src/views/equipmentManagement/inspectionManagement/index.vue b/src/views/equipmentManagement/inspectionManagement/index.vue
index 3e4e31e..63ff08e 100644
--- a/src/views/equipmentManagement/inspectionManagement/index.vue
+++ b/src/views/equipmentManagement/inspectionManagement/index.vue
@@ -33,15 +33,21 @@
         </el-space>
       </div>
       <div>
-        <div>
-          <PIMTable :table-loading="tableLoading"
-                  :table-data="tableData"
-                  :column="tableColumns"
-                  @selection-change="handleSelectionChange"
-                  :is-selection="true"
-                  :border="true"
-                  :table-style="{ width: '100%', height: 'calc(100vh - 23em)' }"
-          >
+        <PIMTable :table-loading="tableLoading"
+                :table-data="tableData"
+                :column="tableColumns"
+                @selection-change="handleSelectionChange"
+                @pagination="handlePagination"
+                :is-selection="true"
+                :border="true"
+                :page="{
+                  current: pageNum,
+                  size: pageSize,
+                  total: total,
+                  layout: 'total, sizes, prev, pager, next, jumper'
+                }"
+                :table-style="{ width: '100%', height: 'calc(100vh - 23em)' }"
+        >
           <template #inspector="{ row }">
             <div class="person-tags">
               <!-- 璋冭瘯淇℃伅锛屼笂绾挎椂鍒犻櫎 -->
@@ -60,16 +66,7 @@
               <span v-else class="no-data">--</span>
             </div>
           </template>
-            </PIMTable>
-        </div>
-        <pagination
-            v-if="total>0"
-            :page="pageNum"
-            :limit="pageSize"
-            :total="total"
-            @pagination="handlePagination"
-            :layout="'total, prev, pager, next, jumper'"
-        />
+        </PIMTable>
       </div>
     </el-card>
     <form-dia ref="formDia" @closeDia="handleQuery"></form-dia>
@@ -83,7 +80,6 @@
 import { ElMessageBox } from "element-plus";
 
 // 缁勪欢寮曞叆
-import Pagination from "@/components/Pagination/index.vue";
 import PIMTable from "@/components/PIMTable/PIMTable.vue";
 import FormDia from "@/views/equipmentManagement/inspectionManagement/components/formDia.vue";
 import ViewFiles from "@/views/equipmentManagement/inspectionManagement/components/viewFiles.vue";
@@ -207,7 +203,17 @@
     operationsArr.value = ['edit'];
   } else if (value === "task") {
     const operationColumn = getOperationColumn(['viewFile']);
-    tableColumns.value = [...columns.value, ...(operationColumn ? [operationColumn] : [])];
+    const statusColumn = {
+      prop: "status",
+      label: "浠诲姟鐘舵��",
+      minWidth: 100,
+      dataType: "tag",
+      formatType: (row) => {
+        if (row.status === '宸插贰妫�') return 'success';
+        return 'warning';
+      }
+    };
+    tableColumns.value = [...columns.value, statusColumn, ...(operationColumn ? [operationColumn] : [])];
     operationsArr.value = ['viewFile'];
   }
   pageNum.value = 1;
@@ -224,7 +230,7 @@
 // 鍒嗛〉澶勭悊
 const handlePagination = (val) => {
 	pageNum.value = val.page;
-	pageSize.value = val.size;
+	pageSize.value = val.limit;
 	getList();
 };
 // 鑾峰彇鍒楄〃鏁版嵁
diff --git a/src/views/equipmentManagement/ledger/Form.vue b/src/views/equipmentManagement/ledger/Form.vue
index d14f4ff..fe204a7 100644
--- a/src/views/equipmentManagement/ledger/Form.vue
+++ b/src/views/equipmentManagement/ledger/Form.vue
@@ -32,71 +32,29 @@
         </el-form-item>
       </el-col>
       <el-col :span="12">
-        <el-form-item label="鍚敤鎶樻棫" prop="enableDepreciation">
-          <el-switch v-model="form.enableDepreciation" :active-value="true" :inactive-value="false" />
-        </el-form-item>
-      </el-col>
-      <el-col :span="12">
         <el-form-item label="鏁伴噺" prop="number">
           <el-input-number :min="1" style="width: 100%"
             v-model="form.number"
-													 disabled
+											 disabled
             placeholder="璇疯緭鍏ユ暟閲�"
-            @change="mathNum"
           />
         </el-form-item>
       </el-col>
       <el-col :span="12">
-        <el-form-item label="鍚◣鍗曚环" prop="taxIncludingPriceUnit">
-          <el-input-number :step="0.01" :min="0" style="width: 100%"
+        <el-form-item label="璧勪骇鍘熷��" prop="taxIncludingPriceUnit">
+          <el-input-number :min="0" style="width: 100%"
+													 :precision="2"
             v-model="form.taxIncludingPriceUnit"
-            placeholder="璇疯緭鍏ュ惈绋庡崟浠�"
+            placeholder="璇疯緭鍏ヨ祫浜у師鍊�"
             maxlength="10"
-            @change="mathNum"
           />
         </el-form-item>
       </el-col>
-      <el-col :span="12">
-        <el-form-item label="鍚◣鎬讳环" prop="taxIncludingPriceTotal">
-          <el-input
-            v-model="form.taxIncludingPriceTotal"
-            placeholder="鑷姩鐢熸垚"
-            type="number"
-            disabled
-          />
-        </el-form-item>
-      </el-col>
-      <el-col :span="12">
-        <el-form-item label="绋庣巼(%)" prop="taxRate">
-          <!-- <el-input
-            v-model="form.taxRate"
-            placeholder="璇疯緭鍏ョ◣鐜�"
-            type="number"
-          >
-            <template #append> % </template>
-          </el-input> -->
-          <el-select
-            v-model="form.taxRate"
-            placeholder="璇烽�夋嫨"
-            clearable
-            @change="mathNum"
-          >
-            <el-option label="1" :value="1" />
-            <el-option label="6" :value="6" />
-            <el-option label="13" :value="13" />
-          </el-select>
-        </el-form-item>
-      </el-col>
-      <el-col :span="12">
-        <el-form-item label="涓嶅惈绋庢�讳环" prop="unTaxIncludingPriceTotal">
-          <el-input
-            v-model="form.unTaxIncludingPriceTotal"
-            placeholder="鑷姩鐢熸垚"
-            type="number"
-            disabled
-          />
-        </el-form-item>
-      </el-col>
+			<el-col :span="12">
+				<el-form-item label="鍚敤鎶樻棫" prop="enableDepreciation">
+					<el-switch v-model="form.enableDepreciation" :active-value="true" :inactive-value="false" />
+				</el-form-item>
+			</el-col>
       <!-- <el-col :span="12">
         <el-form-item label="褰曞叆浜�" prop="createUser">
           <el-input v-model="form.createUser" placeholder="璇疯緭鍏ュ綍鍏ヤ汉" />
@@ -137,11 +95,6 @@
 // import useUserStore from "@/store/modules/user";
 import { getLedgerById } from "@/api/equipmentManagement/ledger";
 import dayjs from "dayjs";
-import {
-  calculateTaxIncludeTotalPrice,
-  calculateTaxExclusiveTotalPrice,
-} from "@/utils/summarizeTable";
-import { ElMessage } from "element-plus";
 import {ref} from "vue";
 
 defineOptions({
@@ -151,13 +104,25 @@
 const operationType = ref('');
 const formRules = {
 	deviceName: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
-	deviceModel: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
-	supplierName: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
-	unit: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
-	number: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
-	taxIncludingPriceUnit: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
-	taxRate: [{ required: true, trigger: "change", message: "璇疯緭鍏�" }],
-	planRuntimeTime: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
+	deviceModel: [{ trigger: "blur", message: "璇疯緭鍏�" }],
+	supplierName: [{ trigger: "blur", message: "璇疯緭鍏�" }],
+	unit: [{ trigger: "blur", message: "璇疯緭鍏�" }],
+	taxIncludingPriceUnit: [
+		{
+			required: true,
+			trigger: "blur",
+			validator: (rule, value, callback) => {
+				if (value === undefined || value === null || value === '') {
+					callback(new Error("璇疯緭鍏ヨ祫浜у師鍊�"));
+				} else if (typeof value === 'number' && value >= 0) {
+					callback();
+				} else {
+					callback(new Error("璇疯緭鍏ユ湁鏁堢殑璧勪骇鍘熷��"));
+				}
+			}
+		}
+	],
+	planRuntimeTime: [{ trigger: "change", message: "璇烽�夋嫨" }],
 }
 
 const { form, resetForm } = useFormData({
@@ -169,10 +134,7 @@
   enableDepreciation: false, // 鏄惁鍚敤鎶樻棫
   unit: undefined, // 鍗曚綅
   number: 1, // 鏁伴噺
-  taxIncludingPriceUnit: undefined, // 鍚◣鍗曚环
-  taxIncludingPriceTotal: undefined, // 鍚◣鎬讳环
-  taxRate: undefined, // 绋庣巼
-  unTaxIncludingPriceTotal: undefined, // 涓嶅惈绋庢�讳环
+	taxIncludingPriceUnit: undefined, // 璧勪骇鍘熷��
   // createUser: useUserStore().nickName, // 褰曞叆浜�
   createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), // 褰曞叆鏃ユ湡
 	planRuntimeTime: dayjs().format("YYYY-MM-DD"), // 褰曞叆鏃ユ湡
@@ -193,29 +155,11 @@
     form.unit = data.unit;
     form.number = 1;
     form.taxIncludingPriceUnit = data.taxIncludingPriceUnit;
-    form.taxIncludingPriceTotal = data.taxIncludingPriceTotal;
-    form.taxRate = data.taxRate;
-    form.unTaxIncludingPriceTotal = data.unTaxIncludingPriceTotal;
     form.createTime = data.createTime;
   }
 };
 
-const mathNum = () => {
-  if (!form.taxIncludingPriceUnit) {
-    ElMessage.error("璇疯緭鍏ュ崟浠�");
-    return;
-  }
-  form.taxIncludingPriceTotal = calculateTaxIncludeTotalPrice(
-    form.taxIncludingPriceUnit,
-    form.number
-  );
-  if (form.taxRate) {
-    form.unTaxIncludingPriceTotal = calculateTaxExclusiveTotalPrice(
-      form.taxIncludingPriceTotal,
-      form.taxRate
-    );
-  }
-};
+
 
 // 娓呴櫎琛ㄥ崟鏍¢獙鐘舵��
 const clearValidate = () => {
diff --git a/src/views/equipmentManagement/ledger/index.vue b/src/views/equipmentManagement/ledger/index.vue
index 6af5543..0a2ce3c 100644
--- a/src/views/equipmentManagement/ledger/index.vue
+++ b/src/views/equipmentManagement/ledger/index.vue
@@ -55,6 +55,7 @@
         <div></div>
         <div>
           <el-button type="primary" @click="add" icon="Plus"> 鏂板 </el-button>
+          <el-button plain icon="Upload" @click="handleImport">瀵煎叆</el-button>
           <el-button @click="handleOut" icon="download">瀵煎嚭</el-button>
           <el-button
             type="danger"
@@ -82,15 +83,59 @@
       </PIMTable>
     </div>
     <Modal ref="modalRef" @success="getTableData"></Modal>
-		<el-dialog v-model="qrDialogVisible" title="浜岀淮鐮�" width="300px">
-			<div style="text-align:center;">
-				<img :src="qrCodeUrl" alt="浜岀淮鐮�" style="width:200px;height:200px;" />
-				<div style="margin-top:6px;font-size:14px;color:#333;">{{ qrRowData?.deviceName }}</div>
-				<div style="margin:10px 0;">
-					<el-button type="primary" @click="downloadQRCode">涓嬭浇浜岀淮鐮佸浘鐗�</el-button>
-				</div>
-			</div>
-		</el-dialog>
+    <el-dialog v-model="qrDialogVisible" title="浜岀淮鐮�" width="300px">
+      <div style="text-align:center;">
+        <img :src="qrCodeUrl" alt="浜岀淮鐮�" style="width:200px;height:200px;" />
+        <div style="margin-top:6px;font-size:14px;color:#333;">{{ qrRowData?.deviceName }}</div>
+        <div style="margin:10px 0;">
+          <el-button type="primary" @click="downloadQRCode">涓嬭浇浜岀淮鐮佸浘鐗�</el-button>
+        </div>
+      </div>
+    </el-dialog>
+    <!-- 鐢ㄦ埛瀵煎叆瀵硅瘽妗� -->
+    <el-dialog
+      :title="upload.title"
+      v-model="upload.open"
+      width="400px"
+      append-to-body
+    >
+      <el-upload
+        ref="uploadRef"
+        :limit="1"
+        accept=".xlsx, .xls"
+        :headers="upload.headers"
+        :action="upload.url + '?updateSupport=' + upload.updateSupport"
+        :disabled="upload.isUploading"
+        :before-upload="upload.beforeUpload"
+        :on-progress="upload.onProgress"
+        :on-success="upload.onSuccess"
+        :on-error="upload.onError"
+        :on-change="upload.onChange"
+        :auto-upload="false"
+        drag
+      >
+        <el-icon class="el-icon--upload"><upload-filled /></el-icon>
+        <div class="el-upload__text">灏嗘枃浠舵嫋鍒版澶勶紝鎴�<em>鐐瑰嚮涓婁紶</em></div>
+        <template #tip>
+          <div class="el-upload__tip text-center">
+            <span>浠呭厑璁稿鍏ls銆亁lsx鏍煎紡鏂囦欢銆�</span>
+            <el-link
+              type="primary"
+              :underline="false"
+              style="font-size: 12px; vertical-align: baseline"
+              @click="importTemplate"
+              >涓嬭浇妯℃澘</el-link
+            >
+          </div>
+        </template>
+      </el-upload>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitFileForm">纭� 瀹�</el-button>
+          <el-button @click="upload.open = false">鍙� 娑�</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
@@ -104,6 +149,7 @@
 import dayjs from "dayjs";
 import QRCode from "qrcode";
 import { ref } from "vue";
+import { getToken } from "@/utils/auth.js";
 
 defineOptions({
   name: "璁惧鍙拌处",
@@ -172,24 +218,9 @@
       prop: "number",
     },
     {
-      label: "鍚◣鍗曚环",
+      label: "璧勪骇鍘熷��",
       align: "center",
       prop: "taxIncludingPriceUnit",
-    },
-    {
-      label: "鍚◣鎬讳环",
-      align: "center",
-      prop: "taxIncludingPriceTotal",
-    },
-    {
-      label: "绋庣巼",
-      align: "center",
-      prop: "taxRate",
-    },
-    {
-      label: "涓嶅惈绋庢�讳环",
-      align: "center",
-      prop: "unTaxIncludingPriceTotal",
     },
     {
       label: "鍚敤鎶樻棫",
@@ -232,6 +263,69 @@
 		},
   ]
 );
+
+const upload = reactive({
+  // 鏄惁鏄剧ず寮瑰嚭灞傦紙瀹㈡埛瀵煎叆锛�
+  open: false,
+  // 寮瑰嚭灞傛爣棰橈紙瀹㈡埛瀵煎叆锛�
+  title: "",
+  // 鏄惁绂佺敤涓婁紶
+  isUploading: false,
+  // 璁剧疆涓婁紶鐨勮姹傚ご閮�
+  headers: { Authorization: "Bearer " + getToken() },
+  // 涓婁紶鐨勫湴鍧�
+  url: import.meta.env.VITE_APP_BASE_API + "/device/ledger/import",
+  // 鏂囦欢涓婁紶鍓嶇殑鍥炶皟
+  beforeUpload: (file) => {
+    console.log('鏂囦欢鍗冲皢涓婁紶', file);
+    // 鍙互鍦ㄦ澶勫仛鏂囦欢绫诲瀷鎴栧ぇ灏忔牎楠�
+    const isValid = file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || file.name.endsWith('.xlsx') || file.name.endsWith('.xls');
+    if (!isValid) {
+      proxy.$modal.msgError("鍙兘涓婁紶 Excel 鏂囦欢");
+    }
+    return isValid;
+  },
+  // 鏂囦欢鐘舵�佹敼鍙樻椂鐨勫洖璋�
+  onChange: (file, fileList) => {
+    console.log('鏂囦欢鐘舵�佹敼鍙�', file, fileList);
+  },
+  // 鏂囦欢涓婁紶鎴愬姛鏃剁殑鍥炶皟
+  onSuccess: (response, file, fileList) => {
+    console.log('涓婁紶鎴愬姛', response, file, fileList);
+    upload.isUploading = false;
+    if(response.code === 200){
+      proxy.$modal.msgSuccess("鏂囦欢涓婁紶鎴愬姛");
+      upload.open = false;
+      proxy.$refs["uploadRef"].clearFiles();
+      getTableData();
+    }else if(response.code === 500){
+      proxy.$modal.msgError(response.msg);
+    }else{
+      proxy.$modal.msgWarning(response.msg);
+    }
+  },
+  // 鏂囦欢涓婁紶澶辫触鏃剁殑鍥炶皟
+  onError: (error, file, fileList) => {
+    console.error('涓婁紶澶辫触', error, file, fileList);
+    upload.isUploading = false;
+    proxy.$modal.msgError("鏂囦欢涓婁紶澶辫触");
+  },
+  // 鏂囦欢涓婁紶杩涘害鍥炶皟
+  onProgress: (event, file, fileList) => {
+    console.log('涓婁紶涓�...', event.percent);
+  }
+});
+
+/** 鎻愪氦涓婁紶鏂囦欢 */
+const submitFileForm = () => {
+  upload.isUploading = true;
+  proxy.$refs["uploadRef"].submit();
+}
+
+/** 涓嬭浇妯℃澘 */
+const importTemplate = () => {
+  proxy.download("/device/ledger/downloadTemplate", {}, "璁惧鍙拌处妯℃澘.xlsx");
+}
 
 // 澶氶�夊悗鍋氫粈涔�
 const handleSelectionChange = (selectionList) => {
@@ -276,6 +370,11 @@
   }
   getTableData();
 };
+/** 瀵煎叆鎸夐挳鎿嶄綔 */
+const handleImport = () => {
+  upload.title = "璁惧鍙拌处瀵煎叆";
+  upload.open = true;
+}
 
 const handleOut = () => {
   ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
@@ -300,44 +399,44 @@
 };
 
 const downloadQRCode = () => {
-	const name = qrRowData.value?.deviceName || "浜岀淮鐮�";
-	const img = new Image();
-	img.src = qrCodeUrl.value;
-	img.onload = () => {
-		const padding = 10;
-		const qrSize = 200;
-		const textHeight = 24; // space for text
-		const width = qrSize + padding * 2;
-		const height = qrSize + padding * 2 + textHeight;
-		const canvas = document.createElement("canvas");
-		canvas.width = width;
-		canvas.height = height;
-		const ctx = canvas.getContext("2d");
-		// background
-		ctx.fillStyle = "#ffffff";
-		ctx.fillRect(0, 0, width, height);
-		// draw QR centered
-		ctx.drawImage(img, padding, padding, qrSize, qrSize);
-		// draw name centered below
-		ctx.fillStyle = "#333";
-		ctx.font = "14px Arial";
-		ctx.textAlign = "center";
-		ctx.textBaseline = "middle";
-		const maxTextWidth = width - padding * 2;
-		let displayName = name;
-		// ellipsis if too long
-		while (ctx.measureText(displayName).width > maxTextWidth && displayName.length > 0) {
-			displayName = displayName.slice(0, -1);
-		}
-		if (displayName !== name) displayName = displayName + "鈥�";
-		ctx.fillText(displayName, width / 2, qrSize + padding + textHeight / 2);
-		
-		const dataUrl = canvas.toDataURL("image/png");
-		const a = document.createElement("a");
-		a.href = dataUrl;
-		a.download = `${name}.png`;
-		a.click();
-	};
+  const name = qrRowData.value?.deviceName || "浜岀淮鐮�";
+  const img = new Image();
+  img.src = qrCodeUrl.value;
+  img.onload = () => {
+    const padding = 10;
+    const qrSize = 200;
+    const textHeight = 24; // space for text
+    const width = qrSize + padding * 2;
+    const height = qrSize + padding * 2 + textHeight;
+    const canvas = document.createElement("canvas");
+    canvas.width = width;
+    canvas.height = height;
+    const ctx = canvas.getContext("2d");
+    // background
+    ctx.fillStyle = "#ffffff";
+    ctx.fillRect(0, 0, width, height);
+    // draw QR centered
+    ctx.drawImage(img, padding, padding, qrSize, qrSize);
+    // draw name centered below
+    ctx.fillStyle = "#333";
+    ctx.font = "14px Arial";
+    ctx.textAlign = "center";
+    ctx.textBaseline = "middle";
+    const maxTextWidth = width - padding * 2;
+    let displayName = name;
+    // ellipsis if too long
+    while (ctx.measureText(displayName).width > maxTextWidth && displayName.length > 0) {
+      displayName = displayName.slice(0, -1);
+    }
+    if (displayName !== name) displayName = displayName + "鈥�";
+    ctx.fillText(displayName, width / 2, qrSize + padding + textHeight / 2);
+
+    const dataUrl = canvas.toDataURL("image/png");
+    const a = document.createElement("a");
+    a.href = dataUrl;
+    a.download = `${name}.png`;
+    a.click();
+  };
 };
 
 onMounted(() => {
diff --git a/src/views/equipmentManagement/repair/Form/MaintainForm.vue b/src/views/equipmentManagement/repair/Form/MaintainForm.vue
deleted file mode 100644
index bbb25c1..0000000
--- a/src/views/equipmentManagement/repair/Form/MaintainForm.vue
+++ /dev/null
@@ -1,58 +0,0 @@
-<template>
-  <el-form :model="form" label-width="80px">
-    <el-form-item label="缁翠慨浜�">
-      <el-input v-model="form.maintenanceName" placeholder="璇疯緭鍏ョ淮淇汉" />
-    </el-form-item>
-    <el-form-item label="缁翠慨缁撴灉">
-      <el-input v-model="form.maintenanceResult" placeholder="璇疯緭鍏ョ淮淇粨鏋�" />
-    </el-form-item>
-    <el-form-item label="缁翠慨鏃ユ湡">
-      <el-date-picker
-        v-model="form.maintenanceTime"
-        placeholder="璇烽�夋嫨缁翠慨鏃ユ湡"
-        format="YYYY-MM-DD HH:mm:ss"
-        value-format="YYYY-MM-DD HH:mm:ss"
-        type="datetime"
-        clearable
-        style="width: 100%"
-      />
-    </el-form-item>
-  </el-form>
-</template>
-
-<script setup>
-import useFormData from "@/hooks/useFormData";
-import useUserStore from "@/store/modules/user";
-import dayjs from "dayjs";
-
-defineOptions({
-  name: "璁惧缁翠慨琛ㄥ崟",
-});
-
-const userStore = useUserStore();
-const { form, resetForm } = useFormData({
-  maintenanceName: undefined, // 缁翠慨鍚嶇О
-  maintenanceResult: undefined, // 缁翠慨缁撴灉
-  maintenanceTime: undefined, // 缁翠慨鏃ユ湡
-});
-
-const setForm = (data) => {
-  form.maintenanceName = data.maintenanceName ?? userStore.nickName;
-  form.maintenanceResult = data.maintenanceResult;
-  form.maintenanceTime =
-    dayjs(data.maintenanceTime).format("YYYY-MM-DD HH:mm:ss") ??
-    dayjs().format("YYYY-MM-DD HH:mm:ss");
-};
-
-const getForm = () => {
-  return form;
-};
-
-defineExpose({
-  getForm,
-  setForm,
-  resetForm,
-});
-</script>
-
-<style lang="scss" scoped></style>
diff --git a/src/views/equipmentManagement/repair/Form/RepairForm.vue b/src/views/equipmentManagement/repair/Form/RepairForm.vue
deleted file mode 100644
index 1fadde4..0000000
--- a/src/views/equipmentManagement/repair/Form/RepairForm.vue
+++ /dev/null
@@ -1,113 +0,0 @@
-<template>
-  <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">
-            <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-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-form>
-</template>
-
-<script setup>
-import useFormData from "@/hooks/useFormData";
-import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
-import useUserStore from "@/store/modules/user";
-
-defineOptions({
-  name: "璁惧鎶ヤ慨琛ㄥ崟",
-});
-
-const userStore = useUserStore();
-const deviceOptions = ref([]);
-
-const loadDeviceName = async () => {
-  const { data } = await getDeviceLedger();
-  deviceOptions.value = data;
-};
-
-const { form, resetForm } = useFormData({
-  deviceLedgerId: undefined, // 璁惧Id
-  deviceName: undefined, // 璁惧鍚嶇О
-  deviceModel: undefined, // 瑙勬牸鍨嬪彿
-  repairTime: undefined, // 鎶ヤ慨鏃ユ湡
-  repairName: userStore.nickName, // 鎶ヤ慨浜�
-  remark: undefined, // 鏁呴殰鐜拌薄
-});
-
-const setDeviceModel = (id) => {
-  const option = deviceOptions.value.find((item) => item.id === id);
-  form.deviceModel = option.deviceModel;
-};
-
-const getForm = () => {
-  return form;
-};
-
-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;
-};
-
-// onMounted(() => {
-//   loadDeviceName();
-// });
-
-defineExpose({
-  loadDeviceName,
-  resetForm,
-  getForm,
-  setForm,
-});
-</script>
-
-<style lang="scss" scoped></style>
diff --git a/src/views/equipmentManagement/repair/Modal/ApproveModal.vue b/src/views/equipmentManagement/repair/Modal/ApproveModal.vue
new file mode 100644
index 0000000..74f3605
--- /dev/null
+++ b/src/views/equipmentManagement/repair/Modal/ApproveModal.vue
@@ -0,0 +1,142 @@
+<template>
+  <FormDialog
+    v-model="visible"
+    title="鎶ヤ慨瀹℃壒"
+    width="800px"
+    @confirm="handleSubmit"
+    @cancel="handleClose"
+    @close="handleClose"
+  >
+    <el-descriptions :column="2" border>
+      <el-descriptions-item label="璁惧鍚嶇О">
+        {{ detail.deviceName || "-" }}
+      </el-descriptions-item>
+      <el-descriptions-item label="瑙勬牸鍨嬪彿">
+        {{ detail.deviceModel || "-" }}
+      </el-descriptions-item>
+      <el-descriptions-item label="鎶ヤ慨鏃ユ湡">
+        {{ detail.repairTime || "-" }}
+      </el-descriptions-item>
+      <el-descriptions-item label="鎶ヤ慨浜�">
+        {{ detail.repairName || "-" }}
+      </el-descriptions-item>
+      <el-descriptions-item label="瀹℃壒浜�">
+        {{ detail.auditName || "-" }}
+      </el-descriptions-item>
+      <el-descriptions-item label="褰撳墠鐘舵��">
+        {{ statusText(detail.status) }}
+      </el-descriptions-item>
+      <el-descriptions-item label="鏁呴殰鐜拌薄" :span="2">
+        {{ detail.remark || "-" }}
+      </el-descriptions-item>
+    </el-descriptions>
+    <div style="margin-top: 16px">
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="瀹℃壒缁撴灉" prop="decision">
+          <el-radio-group v-model="form.decision">
+            <el-radio :value="0">閫氳繃</el-radio>
+            <el-radio :value="3">涓嶉�氳繃</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="鐩戠潱浜�" prop="supervisoryName">
+          <el-input v-model="form.supervisoryName" placeholder="璇疯緭鍏ョ洃鐫d汉" clearable style="width: 100%" />
+        </el-form-item>
+      </el-form>
+    </div>
+  </FormDialog>
+</template>
+
+<script setup>
+import { nextTick, ref } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+import { editRepair, getRepairById } from "@/api/equipmentManagement/repair";
+
+defineOptions({
+  name: "鎶ヤ慨瀹℃壒寮圭獥",
+});
+
+const emits = defineEmits(["ok"]);
+
+const visible = ref(false);
+const loading = ref(false);
+const id = ref();
+const detail = ref({});
+const formRef = ref();
+const form = ref({
+  decision: undefined, // 0 閫氳繃 3 涓嶉�氳繃
+  supervisoryName: undefined, // 鐩戠潱浜�
+});
+
+const rules = {
+  decision: [{ required: true, message: "璇烽�夋嫨瀹℃壒缁撴灉", trigger: "change" }],
+  supervisoryName: [{ required: true, message: "璇烽�夋嫨鐩戠潱浜�", trigger: "change" }],
+};
+
+const statusText = (status) => {
+  const map = {
+    0: "寰呯淮淇�",
+    1: "瀹岀粨",
+    2: "寰呭鏍�",
+    3: "瀹℃牳涓嶉�氳繃",
+  };
+  return map[status] ?? "-";
+};
+
+const loadDetail = async (repairId) => {
+  const { data } = await getRepairById(repairId);
+  detail.value = data ?? {};
+};
+
+const open = async (repairId) => {
+  id.value = repairId;
+  visible.value = true;
+  await nextTick();
+  await loadDetail(repairId);
+  form.value.decision = undefined;
+  form.value.supervisoryName = undefined;
+};
+
+const handleClose = () => {
+  visible.value = false;
+  id.value = undefined;
+  detail.value = {};
+  form.value.decision = undefined;
+  form.value.supervisoryName = undefined;
+};
+
+const updateStatus = async (status) => {
+  loading.value = true;
+  try {
+    const { code } = await editRepair({ id: id.value, status, supervisoryName: form.value.supervisoryName });
+    if (code === 200) {
+      ElMessage.success("瀹℃壒鎴愬姛");
+      emits("ok");
+      handleClose();
+    }
+  } finally {
+    loading.value = false;
+  }
+};
+
+const handleSubmit = async () => {
+  if (detail.value?.status !== 2) {
+    ElMessage.warning("浠呭緟瀹℃牳鐘舵�佸彲瀹℃壒");
+    return;
+  }
+  await formRef.value?.validate(async (valid) => {
+    if (!valid) return;
+    const isApprove = form.value.decision === 0;
+    ElMessageBox.confirm(`纭瀹℃壒${isApprove ? "閫氳繃" : "涓嶉�氳繃"}锛焋, "鎻愮ず", {
+      confirmButtonText: "纭畾",
+      cancelButtonText: "鍙栨秷",
+      type: "warning",
+    }).then(() => updateStatus(form.value.decision));
+  });
+};
+
+defineExpose({ open });
+</script>
+
+<style scoped></style>
+
diff --git a/src/views/equipmentManagement/repair/Modal/MaintainModal.vue b/src/views/equipmentManagement/repair/Modal/MaintainModal.vue
index 309be0e..7058a5a 100644
--- a/src/views/equipmentManagement/repair/Modal/MaintainModal.vue
+++ b/src/views/equipmentManagement/repair/Modal/MaintainModal.vue
@@ -1,53 +1,116 @@
 <template>
-  <el-dialog v-model="visible" :title="modalOptions.title" direction="ltr">
-    <MaintainForm ref="maintainFormRef" />
-    <template #footer>
-			<el-button type="primary" @click="sendForm" :loading="loading">
-				{{ modalOptions.confirmText }}
-			</el-button>
-      <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button>
-    </template>
-  </el-dialog>
+  <FormDialog
+    v-model="visible"
+    :title="'璁惧缁翠慨'"
+    width="500px"
+    @confirm="sendForm"
+    @cancel="handleCancel"
+    @close="handleClose"
+  >
+    <el-form :model="form" :rules="rules" label-width="110px" ref="formRef">
+      <el-form-item label="缁翠慨浜�" prop="maintenanceName">
+        <el-input v-model="form.maintenanceName" placeholder="璇疯緭鍏ョ淮淇汉" />
+      </el-form-item>
+      <el-form-item label="缁翠慨缁撴灉" prop="maintenanceResult">
+        <el-input v-model="form.maintenanceResult" placeholder="璇疯緭鍏ョ淮淇粨鏋�" />
+      </el-form-item>
+			<el-form-item label="鏈缁翠慨閲戦" prop="repairPrice">
+				<el-input-number v-model="form.repairPrice" :min="0" :precision="2" style="width: 100%" />
+			</el-form-item>
+      <el-form-item label="缁翠慨鏃ユ湡" prop="maintenanceTime">
+        <el-date-picker
+          v-model="form.maintenanceTime"
+          placeholder="璇烽�夋嫨缁翠慨鏃ユ湡"
+          format="YYYY-MM-DD HH:mm:ss"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="datetime"
+          clearable
+          style="width: 100%"
+        />
+      </el-form-item>
+    </el-form>
+  </FormDialog>
 </template>
 
 <script setup>
-import { useModal } from "@/hooks/useModal";
-import MaintainForm from "../Form/MaintainForm.vue";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
 import { addMaintain } 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 maintainFormRef = ref();
 const emits = defineEmits(["ok"]);
 
-const {
-  id,
-  visible,
-  loading,
-  openModal,
-  modalOptions,
-  handleConfirm,
-  closeModal,
-} = useModal({ title: "璁惧缁翠慨" });
+// 淇濆瓨鎶ヤ慨璁板綍鐨刬d
+const repairId = ref();
+const visible = ref(false);
+const loading = ref(false);
+const formRef = ref();
+
+const userStore = useUserStore();
+const { form, resetForm } = useFormData({
+  maintenanceName: undefined, // 缁翠慨鍚嶇О
+  maintenanceResult: undefined, // 缁翠慨缁撴灉
+  maintenanceTime: undefined, // 缁翠慨鏃ユ湡
+	repairPrice: undefined, // 缁翠慨閲戦
+  status: 0,
+});
+
+const rules = {
+  maintenanceName: [{ required: true, message: "璇疯緭鍏ョ淮淇汉", trigger: "blur" }],
+  maintenanceResult: [{ required: true, message: "璇疯緭鍏ョ淮淇粨鏋�", trigger: "blur" }],
+  repairPrice: [{ required: true, message: "璇疯緭鍏ユ湰娆$淮淇噾棰�", trigger: "change" }],
+  maintenanceTime: [{ required: true, message: "璇烽�夋嫨缁翠慨鏃ユ湡", trigger: "change" }],
+};
+
+const setForm = (data) => {
+  form.maintenanceName = data.maintenanceName ?? userStore.nickName;
+  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 = 2; // 缁翠慨鍚庡浐瀹氳繘鍏ュ緟瀹℃牳锛堜笉鍦ㄧ晫闈㈠睍绀猴級
+};
 
 const sendForm = async () => {
-  loading.value = true;
-  const form = await maintainFormRef.value.getForm();
-  const { code } = await addMaintain({ id: id.value, ...form });
-  if (code == 200) {
-    emits("ok");
-    maintainFormRef.value.resetForm();
-    closeModal();
-  }
-  loading.value = false;
+	await formRef.value.validate(async (valid) => {
+		if (!valid) return;
+		loading.value = true;
+		try {
+			const { code } = await addMaintain({ id: repairId.value, ...form });
+			if (code == 200) {
+				ElMessage.success("缁翠慨鎴愬姛");
+				emits("ok");
+				resetForm();
+				visible.value = false;
+			}
+		} finally {
+			loading.value = false;
+		}
+	})
+};
+
+const handleCancel = () => {
+  resetForm();
+  visible.value = false;
+};
+
+const handleClose = () => {
+  resetForm();
+  visible.value = false;
 };
 
 const open = async (id, row) => {
-  openModal(id);
+  repairId.value = id; // 淇濆瓨鎶ヤ慨璁板綍鐨刬d
+  visible.value = true;
   await nextTick();
-  maintainFormRef.value.setForm(row);
+  setForm(row);
 };
 
 defineExpose({
diff --git a/src/views/equipmentManagement/repair/Modal/RepairModal.vue b/src/views/equipmentManagement/repair/Modal/RepairModal.vue
index 8441da2..20c9c8d 100644
--- a/src/views/equipmentManagement/repair/Modal/RepairModal.vue
+++ b/src/views/equipmentManagement/repair/Modal/RepairModal.vue
@@ -1,24 +1,113 @@
 <template>
-  <el-dialog v-model="visible" :title="modalOptions.title" @close="close">
-    <RepairForm ref="repairFormRef" />
-    <template #footer>
-			<el-button type="primary" @click="sendForm" :loading="loading">
-				{{ modalOptions.confirmText }}
-			</el-button>
-      <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button>
-    </template>
-  </el-dialog>
+  <FormDialog
+    v-model="visible"
+    :title="id ? '缂栬緫璁惧鎶ヤ慨' : '鏂板璁惧鎶ヤ慨'"
+    width="800px"
+    @confirm="sendForm"
+    @cancel="handleCancel"
+    @close="handleClose"
+  >
+    <el-form :model="form" :rules="rules" label-width="100px" ref="formRef">
+      <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-col :span="12">
+          <el-form-item label="瀹℃壒浜�" prop="auditName">
+            <el-select
+              v-model="form.auditName"
+              placeholder="璇烽�夋嫨瀹℃壒浜�"
+              filterable
+              clearable
+              style="width: 100%"
+            >
+              <el-option
+                v-for="user in userOptions"
+                :key="user.userId"
+                :label="user.nickName"
+                :value="user.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-option label="瀹℃牳涓嶉�氳繃" :value="3"></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-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 dayjs from "dayjs";
+import useFormData from "@/hooks/useFormData";
+import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
+import useUserStore from "@/store/modules/user";
+import { userListNoPageByTenantId } from "@/api/system/user";
 
 defineOptions({
   name: "璁惧鎶ヤ慨寮圭獥",
@@ -26,48 +115,101 @@
 
 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 formRef = ref();
+
+const userStore = useUserStore();
+const deviceOptions = ref([]);
+const userOptions = ref([]);
+
+const loadDeviceName = async () => {
+  const { data } = await getDeviceLedger();
+  deviceOptions.value = data;
+};
+
+const loadUserOptions = async () => {
+  const res = await userListNoPageByTenantId();
+  userOptions.value = res?.data ?? [];
+};
+
+const { form, resetForm } = useFormData({
+  deviceLedgerId: undefined, // 璁惧Id
+  deviceName: undefined, // 璁惧鍚嶇О
+  deviceModel: undefined, // 瑙勬牸鍨嬪彿
+  repairTime: dayjs().format("YYYY-MM-DD"), // 鎶ヤ慨鏃ユ湡锛岄粯璁ゅ綋澶�
+  repairName: userStore.nickName, // 鎶ヤ慨浜�
+  auditName: undefined, // 瀹℃壒浜�
+  remark: undefined, // 鏁呴殰鐜拌薄
+  status: 0, // 鎶ヤ慨鐘舵��
+});
+
+const rules = {
+  auditName: [
+    { required: true, message: "璇烽�夋嫨瀹℃壒浜�", trigger: "change" },
+  ],
+};
+
+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.repairName = data.repairName;
+  form.auditName = data.auditName;
+  form.remark = data.remark;
+  form.status = data.status;
+};
 
 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");
-  }
-  loading.value = false;
+  await formRef.value?.validate(async (valid) => {
+    if (!valid) 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");
+      }
+    } finally {
+      loading.value = false;
+    }
+  });
+};
+
+const handleCancel = () => {
+  resetForm();
+  visible.value = false;
+};
+
+const handleClose = () => {
+  resetForm();
+  visible.value = false;
 };
 
 const openAdd = async () => {
-  openModal();
+  id.value = undefined;
+  visible.value = true;
   await nextTick();
-  await repairFormRef.value.loadDeviceName();
+  await Promise.all([loadDeviceName(), loadUserOptions()]);
 };
 
-const openEdit = async (id) => {
-  const { data } = await getRepairById(id);
-  openModal(id);
+const openEdit = async (editId) => {
+  const { data } = await getRepairById(editId);
+  id.value = editId;
+  visible.value = true;
   await nextTick();
-  await repairFormRef.value.loadDeviceName();
-  await repairFormRef.value.setForm(data);
-};
-
-const close = () => {
-  repairFormRef.value.resetForm();
-  closeModal();
+  await Promise.all([loadDeviceName(), loadUserOptions()]);
+  setForm(data);
 };
 
 defineExpose({
@@ -75,3 +217,5 @@
   openEdit,
 });
 </script>
+
+<style lang="scss" scoped></style>
diff --git a/src/views/equipmentManagement/repair/index.vue b/src/views/equipmentManagement/repair/index.vue
index 1a2ff1e..35391e0 100644
--- a/src/views/equipmentManagement/repair/index.vue
+++ b/src/views/equipmentManagement/repair/index.vue
@@ -7,7 +7,6 @@
             style="width: 240px"
             placeholder="璇疯緭鍏ヨ澶囧悕绉�"
             clearable
-            :prefix-icon="Search"
             @change="getTableData"
         />
       </el-form-item>
@@ -15,9 +14,8 @@
         <el-input
             v-model="filters.deviceModel"
             style="width: 240px"
-            placeholder="璇烽�夋嫨瑙勬牸鍨嬪彿"
+            placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�"
             clearable
-            :prefix-icon="Search"
             @change="getTableData"
         />
       </el-form-item>
@@ -27,7 +25,6 @@
             style="width: 240px"
             placeholder="璇疯緭鍏ユ晠闅滅幇璞�"
             clearable
-            :prefix-icon="Search"
             @change="getTableData"
         />
       </el-form-item>
@@ -37,7 +34,6 @@
             style="width: 240px"
             placeholder="璇疯緭鍏ョ淮淇汉"
             clearable
-            :prefix-icon="Search"
             @change="getTableData"
         />
       </el-form-item>
@@ -68,14 +64,6 @@
       <div class="actions">
         <el-text class="mx-1" size="large">璁惧鎶ヤ慨</el-text>
         <div>
-          <el-button
-            type="primary"
-            icon="Plus"
-            :disabled="multipleList.length !== 1"
-            @click="addMaintain"
-          >
-            鏂板缁翠慨
-          </el-button>
           <el-button type="success" icon="Van" @click="addRepair">
             鏂板鎶ヤ慨
           </el-button>
@@ -85,7 +73,7 @@
           <el-button
             type="danger"
             icon="Delete"
-            :disabled="multipleList.length <= 0"
+            :disabled="multipleList.length <= 0 || hasFinishedStatus"
             @click="delRepairByIds(multipleList.map((item) => item.id))"
           >
             鎵归噺鍒犻櫎
@@ -93,35 +81,53 @@
         </div>
       </div>
       <PIMTable
-        rowKey="id"
-        isSelection
-        :column="columns"
-        :tableData="dataList"
-        :page="{
+          rowKey="id"
+          isSelection
+          :column="columns"
+          :tableData="dataList"
+          :page="{
           current: pagination.currentPage,
           size: pagination.pageSize,
           total: pagination.total,
         }"
-        @selection-change="handleSelectionChange"
-        @pagination="changePage"
+          @selection-change="handleSelectionChange"
+          @pagination="changePage"
       >
         <template #statusRef="{ row }">
-          <el-tag v-if="row.status === 1" type="success">瀹岀粨</el-tag>
-          <el-tag v-if="row.status === 0" type="danger">寰呯淮淇�</el-tag>
+          <el-tag v-if="row.status === 0" type="warning">寰呯淮淇�</el-tag>
+          <el-tag v-else-if="row.status === 1" type="success">瀹岀粨</el-tag>
+          <el-tag v-else-if="row.status === 2" type="info">寰呭鏍�</el-tag>
+          <el-tag v-else-if="row.status === 3" type="danger">瀹℃牳涓嶉�氳繃</el-tag>
         </template>
         <template #operation="{ row }">
           <el-button
             type="primary"
-            text
-            icon="editPen"
+            link
+            :disabled="row.status === 1"
             @click="editRepair(row.id)"
           >
             缂栬緫
           </el-button>
           <el-button
+            type="warning"
+            link
+            :disabled="row.status !== 2"
+            @click="openApprove(row.id)"
+          >
+            瀹℃壒
+          </el-button>
+          <el-button
+            type="success"
+            link
+            :disabled="row.status !== 0"
+            @click="addMaintain(row)"
+          >
+            缁翠慨
+          </el-button>
+          <el-button
             type="danger"
-            text
-            icon="delete"
+            link
+            :disabled="row.status === 1"
             @click="delRepairByIds(row.id)"
           >
             鍒犻櫎
@@ -129,29 +135,32 @@
         </template>
       </PIMTable>
     </div>
-    <RepairModal ref="repairModalRef" @ok="getTableData" />
-    <MaintainModal ref="maintainModalRef" @ok="getTableData" />
+    <RepairModal ref="repairModalRef" @ok="getTableData"/>
+    <MaintainModal ref="maintainModalRef" @ok="getTableData"/>
+    <ApproveModal ref="approveModalRef" @ok="getTableData"/>
   </div>
 </template>
 
 <script setup>
-import { usePaginationApi } from "@/hooks/usePaginationApi";
-import { getRepairPage, delRepair } from "@/api/equipmentManagement/repair";
-import { onMounted, getCurrentInstance } from "vue";
+import { onMounted, getCurrentInstance, computed } from "vue";
+import {usePaginationApi} from "@/hooks/usePaginationApi";
+import {getRepairPage, delRepair} from "@/api/equipmentManagement/repair";
 import RepairModal from "./Modal/RepairModal.vue";
-import { ElMessageBox, ElMessage } from "element-plus";
+import {ElMessageBox, ElMessage} from "element-plus";
 import dayjs from "dayjs";
 import MaintainModal from "./Modal/MaintainModal.vue";
+import ApproveModal from "./Modal/ApproveModal.vue";
 
 defineOptions({
   name: "璁惧鎶ヤ慨",
 });
 
-const { proxy } = getCurrentInstance();
+const {proxy} = getCurrentInstance();
 
 // 妯℃�佹瀹炰緥
 const repairModalRef = ref();
 const maintainModalRef = ref();
+const approveModalRef = ref();
 
 // 琛ㄦ牸澶氶�夋閫変腑椤�
 const multipleList = ref([]);
@@ -166,85 +175,87 @@
   resetFilters,
   onCurrentChange,
 } = usePaginationApi(
-  getRepairPage,
-  {
-    deviceName: undefined,
-    deviceModel: undefined,
-    remark: undefined,
-    maintenanceName: undefined,
-    repairTimeStr: undefined,
-    maintenanceTimeStr: undefined,
-  },
-  [
+    getRepairPage,
     {
-      label: "璁惧鍚嶇О",
-      align: "center",
-      prop: "deviceName",
+      deviceName: undefined,
+      deviceModel: undefined,
+      remark: undefined,
+      maintenanceName: undefined,
+      repairTimeStr: undefined,
+      maintenanceTimeStr: undefined,
     },
-    {
-      label: "瑙勬牸鍨嬪彿",
-      align: "center",
-      prop: "deviceModel",
-    },
-    {
-      label: "鎶ヤ慨鏃ユ湡",
-      align: "center",
-      prop: "repairTime",
-      formatData: (cell) => dayjs(cell).format("YYYY-MM-DD"),
-    },
-    {
-      label: "鎶ヤ慨浜�",
-      align: "center",
-      prop: "repairName",
-    },
-    {
-      label: "鏁呴殰鐜拌薄",
-      align: "center",
-      prop: "remark",
-    },
-    {
-      label: "缁翠慨浜�",
-      align: "center",
-      prop: "maintenanceName",
-    },
-    {
-      label: "缁翠慨缁撴灉",
-      align: "center",
-      prop: "maintenanceResult",
-    },
-    {
-      label: "缁翠慨鏃ユ湡",
-      align: "center",
-      prop: "maintenanceTime",
-      formatData: (cell) => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""),
-    },
-    {
-      label: "鐘舵��",
-      align: "center",
-      prop: "status",
-      dataType: "slot",
-      slot: "statusRef",
-    },
-    {
-      fixed: "right",
-      label: "鎿嶄綔",
-      dataType: "slot",
-      slot: "operation",
-      align: "center",
-      width: "200px",
-    },
-  ]
+    [
+      {
+        label: "璁惧鍚嶇О",
+        align: "center",
+        prop: "deviceName",
+      },
+      {
+        label: "瑙勬牸鍨嬪彿",
+        align: "center",
+        prop: "deviceModel",
+      },
+      {
+        label: "鎶ヤ慨鏃ユ湡",
+        align: "center",
+        prop: "repairTime",
+        formatData: (cell) => dayjs(cell).format("YYYY-MM-DD"),
+      },
+      {
+        label: "鎶ヤ慨浜�",
+        align: "center",
+        prop: "repairName",
+      },
+      {
+        label: "鏁呴殰鐜拌薄",
+        align: "center",
+        prop: "remark",
+      },
+      {
+        label: "缁翠慨浜�",
+        align: "center",
+        prop: "maintenanceName",
+      },
+      {
+        label: "缁翠慨缁撴灉",
+        align: "center",
+        prop: "maintenanceResult",
+      },
+      {
+        label: "缁翠慨鏃ユ湡",
+        align: "center",
+        prop: "maintenanceTime",
+        formatData: (cell) => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""),
+      },
+      { prop: "auditName", label: "瀹℃牳浜�", width: 120 },
+	{ prop: "supervisoryName", label: "鐩戠潱浜�", width: 120 },
+      {
+        label: "鐘舵��",
+        align: "center",
+        prop: "status",
+        dataType: "slot",
+        slot: "statusRef",
+      },
+      {
+        fixed: "right",
+        label: "鎿嶄綔",
+        dataType: "slot",
+        slot: "operation",
+        align: "center",
+        width: "300px",
+      },
+    ]
 );
 
 // type === 1 缁翠慨 2鎶ヤ慨闂�
-const handleDateChange = (value,type) => {
+const handleDateChange = (value, type) => {
   filters.maintenanceTimeStr = null
   filters.c = null
-  if(type === 1){
+  if (type === 1) {
     if (value) {
       filters.maintenanceTimeStr = dayjs(value).format("YYYY-MM-DD");
     }
-  }else{
+  } else {
     if (value) {
       filters.repairTimeStr = dayjs(value).format("YYYY-MM-DD");
     }
@@ -257,6 +268,11 @@
   multipleList.value = selectionList;
 };
 
+// 妫�鏌ラ�変腑鐨勮褰曚腑鏄惁鏈夊畬缁撶姸鎬佺殑
+const hasFinishedStatus = computed(() => {
+  return multipleList.value.some(item => item.status === 1)
+})
+
 // 鏂板鎶ヤ慨
 const addRepair = () => {
   repairModalRef.value.openAdd();
@@ -268,25 +284,41 @@
 };
 
 // 鏂板缁翠慨
-const addMaintain = () => {
-  const row = multipleList.value[0];
+const addMaintain = (row) => {
   maintainModalRef.value.open(row.id, row);
 };
 
-const changePage = ({ page, limit }) => {
-	pagination.currentPage = page;
-	pagination.pageSize = limit;
-	onCurrentChange(page);
+// 瀹℃壒
+const openApprove = (id) => {
+  approveModalRef.value.open(id);
+};
+
+const changePage = ({page, limit}) => {
+  pagination.currentPage = page;
+  pagination.pageSize = limit;
+  onCurrentChange(page);
 };
 
 // 鍗曡鍒犻櫎
 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;
+  });
+
+  if (hasFinished) {
+    ElMessage.warning('涓嶈兘鍒犻櫎鐘舵�佷负瀹岀粨鐨勮褰�');
+    return;
+  }
+
   ElMessageBox.confirm("纭鍒犻櫎鎶ヤ慨鏁版嵁, 姝ゆ搷浣滀笉鍙��?", "璀﹀憡", {
     confirmButtonText: "纭畾",
     cancelButtonText: "鍙栨秷",
     type: "warning",
   }).then(async () => {
-    const { code } = await delRepair(ids);
+    const {code} = await delRepair(ids);
     if (code === 200) {
       ElMessage.success("鍒犻櫎鎴愬姛");
       getTableData();
@@ -301,12 +333,12 @@
     cancelButtonText: "鍙栨秷",
     type: "warning",
   })
-    .then(() => {
-      proxy.download("/device/repair/export", {}, "璁惧鎶ヤ慨.xlsx");
-    })
-    .catch(() => {
-      ElMessage.info("宸插彇娑�");
-    });
+      .then(() => {
+        proxy.download("/device/repair/export", {}, "璁惧鎶ヤ慨.xlsx");
+      })
+      .catch(() => {
+        ElMessage.info("宸插彇娑�");
+      });
 };
 
 onMounted(() => {
@@ -318,6 +350,7 @@
 .table_list {
   margin-top: unset;
 }
+
 .actions {
   display: flex;
   justify-content: space-between;
diff --git a/src/views/equipmentManagement/spareParts/index.vue b/src/views/equipmentManagement/spareParts/index.vue
index f91d76f..4a48d28 100644
--- a/src/views/equipmentManagement/spareParts/index.vue
+++ b/src/views/equipmentManagement/spareParts/index.vue
@@ -1,65 +1,67 @@
 <template>
   <div class="spare-part-category">
-    <div class="table_list">
-      <div class="actions">
-        <el-text class="mx-1" size="large">璁惧鍒嗙被</el-text>
-        <div>
-          <el-button @click="fetchTreeData" :loading="loading">鍒锋柊</el-button>
-          <el-button type="primary" @click="addCategory" >鏂板</el-button>
-        </div>
-      </div>
-      <el-table
-        v-loading="loading"
-        :data="renderTableData"
-        style="width: 100%; margin-top: 10px;"
-        border
-        row-key="id"
-        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
-      >
-        <el-table-column prop="name" label="鍒嗙被鍚嶇О" width="450">
-          <template #default="{ row }">
-            <span :style="{ paddingLeft: getIndentation(row) + 'px' }">
-              {{ row.name }}
-            </span>
-          </template>
-        </el-table-column>
-        <el-table-column prop="sparePartsNo" label="鍒嗙被缂栧彿" width="200"></el-table-column>
-        <el-table-column prop="status" label="鐘舵��" width="100">
-          <template #default="{ row }">
-            <el-tag type="success" size="small">{{ row.status }}</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column prop="description" label="鎻忚堪" win-width="330"></el-table-column>
-        <el-table-column label="鎿嶄綔" width="180" fixed="right">
-          <template #default="{ row }">
-            <el-button
-              type="text"
-              size="small"
-              @click="() => editCategory(row)"
-              :disabled="loading"
-            >
-              缂栬緫
-            </el-button>
-            <el-button
-              type="text"
-              size="small"
-              @click="() => deleteCategory(row.id)"
-              style="color: #f56c6c;"
-              :disabled="loading"
-            >
-              鍒犻櫎
-            </el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-    </div>
+		<div class="search_form">
+			<el-form :inline="true" :model="queryParams" class="search-form">
+				<el-form-item label="澶囦欢鍚嶇О">
+					<el-input
+						v-model="queryParams.name"
+						placeholder="璇疯緭鍏ュ浠跺悕绉�"
+						clearable
+						style="width: 240px"
+					/>
+				</el-form-item>
+				<el-form-item>
+					<el-button type="primary" @click="handleQuery">鏌ヨ</el-button>
+					<el-button @click="resetQuery">閲嶇疆</el-button>
+				</el-form-item>
+			</el-form>
+			<div>
+				<el-button type="primary" @click="addCategory" >鏂板</el-button>
+			</div>
+		</div>
+
+    <PIMTable
+        rowKey="id"
+        :column="columns"
+        :tableData="renderTableData"
+        :tableLoading="loading"
+        :page="pagination"
+        :isShowPagination="true"
+        @pagination="handleSizeChange"
+    >
+      <template #status="{ row }">
+        <el-tag type="success" size="small">{{ row.status }}</el-tag>
+      </template>
+    </PIMTable>
+    
     <el-dialog title="鍒嗙被绠$悊" v-model="dialogVisible" width="60%">
       <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
-        <el-form-item label="鍒嗙被鍚嶇О" prop="name">
+        <el-form-item label="璁惧" prop="deviceLedgerIds">
+          <el-select
+            v-model="form.deviceLedgerIds"
+            placeholder="璇烽�夋嫨璁惧"
+            filterable
+            default-first-option
+            :reserve-keyword="false"
+            multiple
+            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-form-item label="澶囦欢鍚嶇О" prop="name">
           <el-input v-model="form.name"></el-input>
         </el-form-item>
-        <el-form-item label="鍒嗙被缂栧彿" prop="sparePartsNo">
+        <el-form-item label="澶囦欢缂栧彿" prop="sparePartsNo">
           <el-input v-model="form.sparePartsNo"></el-input>
+        </el-form-item>
+        <el-form-item label="鏁伴噺" prop="quantity">
+          <el-input type="number" v-model="form.quantity"></el-input>
         </el-form-item>
         <el-form-item label="鐘舵��" prop="status">
           <el-select v-model="form.status" placeholder="璇烽�夋嫨鐘舵��">
@@ -70,17 +72,15 @@
         <el-form-item label="鎻忚堪" prop="description">
           <el-input v-model="form.description"></el-input>
         </el-form-item>
-        <el-form-item label="涓婄骇鍒嗙被" prop="parentId">
-          <el-select v-model="form.parentId" placeholder="璇烽�夋嫨涓婄骇鍒嗙被">
-            <el-option label="鏃犱笂绾у垎绫�" :value="null"></el-option>
-            <el-option
-              v-for="(item, index) in categories"
-              :key="index"
-              :label="item.name"
-              :value="item.id"
-              
-            ></el-option>
-          </el-select>
+        <el-form-item label="浠锋牸" prop="price">
+          <el-input-number
+            v-model="form.price"
+            placeholder="璇疯緭鍏ヤ环鏍�"
+            :min="0"
+            :step="0.01"
+            :precision="2"
+            style="width: 100%"
+          ></el-input-number>
         </el-form-item>
       </el-form>
       <template #footer>
@@ -96,7 +96,8 @@
 <script setup>
 import { ref, computed, onMounted, reactive, watch } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
-import { getSparePartsList, addSparePart, editSparePart, delSparePart,getSparePartsTree } from "@/api/equipmentManagement/spareParts";
+import { getSparePartsList, addSparePart, editSparePart, delSparePart } from "@/api/equipmentManagement/spareParts";
+import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
 
 // 鍔犺浇鐘舵��
 const loading = ref(false);
@@ -111,8 +112,74 @@
 // const renderTableData = computed(() => buildTree(categories.value));
 const renderTableData = ref([]);
 const operationType = ref('add')
+// 璁惧閫夐」
+const deviceOptions = ref([]);
 // 琛ㄥ崟寮曠敤
 const formRef = ref(null);
+// 鏌ヨ鍙傛暟
+const queryParams = reactive({
+  name: ''
+});
+// 鍒嗛〉鍙傛暟
+const pagination = reactive({
+  current: 1,
+  size: 10,
+  total: 0
+});
+const columns = ref([
+  {
+    label: "璁惧鍚嶇О",
+    prop: "deviceNameStr",
+  },
+  {
+    label: "澶囦欢鍚嶇О",
+    prop: "name",
+  },
+  {
+    label: "澶囦欢缂栧彿",
+    prop: "sparePartsNo",
+  },
+  {
+    label: "鐘舵��",
+    prop: "status",
+    slot: "status",
+    dataType: "slot",
+  },
+  {
+    label: "浠锋牸",
+    prop: "price",
+  },
+  {
+    label: "鏁伴噺",
+    prop: "quantity",
+  },
+  {
+    label: "鎻忚堪",
+    prop: "description",
+  },
+  {
+    label: "鎿嶄綔",
+    prop: "operation",
+    width: 150,
+    fixed: 'right',
+    align: "center",
+    dataType: "action",
+    operation: [
+      {
+        name: "缂栬緫",
+        clickFun: (row) => {
+          editCategory(row)
+        },
+      },
+      {
+        name: "鍒犻櫎",
+        clickFun: (row) => {
+          deleteCategory(row.id)
+        },
+      },
+    ],
+  },
+]);
 // 琛ㄥ崟鏁版嵁
 const form = reactive({
   id:'',
@@ -120,19 +187,37 @@
   sparePartsNo: '',
   status: '',
   description: '',
-  parentId: null
+  deviceLedgerIds: [],
+  price: null
 });
 
 // 琛ㄥ崟楠岃瘉瑙勫垯
 const rules = reactive({
   name: [
-    { required: true, message: '璇疯緭鍏ュ垎绫诲悕绉�', trigger: 'blur' }
+    { required: true, message: '璇疯緭鍏ュ浠跺悕绉�', trigger: 'blur' }
   ],
   sparePartsNo: [
-    { required: true, message: '璇疯緭鍏ュ垎绫荤紪鍙�', trigger: 'blur' }
+    { required: true, message: '璇疯緭鍏ュ浠剁紪鍙�', trigger: 'blur' }
+  ],
+  quantity:[
+    { required: true, message: '璇疯緭鍏ユ暟閲�', trigger: 'blur' }
   ],
   status: [
     { required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change' }
+  ],
+  deviceLedgerIds: [
+    { 
+      required: true, 
+      message: '璇烽�夋嫨璁惧', 
+      trigger: 'change',
+      validator: (rule, value, callback) => {
+        if (operationType.value === 'add' && (!value || value.length === 0)) {
+          callback(new Error('璇烽�夋嫨璁惧'));
+        } else {
+          callback();
+        }
+      }
+    }
   ]
 });
 // 鑾峰彇缂╄繘閲�
@@ -159,54 +244,100 @@
   });
   return result;
 };
-//鑾峰彇鏍戝舰缁撴瀯
-const fetchTreeData = async () => {
-  fetchCategories();
+// 鑾峰彇鍒楄〃鏁版嵁
+const fetchListData = async () => {
+  loading.value = true;
   try {
-    const res = await getSparePartsTree();
-    if (res.code === 200) {
-      renderTableData.value = res.data;
-    } else {
-      ElMessage.error(res.message || '鑾峰彇鍒嗙被鍒楄〃澶辫触');
+    const params = {
+      current: pagination.current,
+      size: pagination.size
+    };
+    if (queryParams.name) {
+      params.name = queryParams.name;
     }
-  }catch (error) {
-    ElMessage.error('鑾峰彇鍒嗙被鍒楄〃澶辫触');
+    const res = await getSparePartsList(params);
+    if (res.code === 200) {
+      renderTableData.value = res.data.records || [];
+      categories.value = res.data.records || [];
+      pagination.total = res.data.total || 0;
+    }
+  } catch (error) {
+		loading.value = false;
+  } finally {
+    loading.value = false;
   }
 }
 
-// 鑾峰彇鍒嗙被鍒楄〃
-const fetchCategories = async () => {
-  loading.value = true;
+// 鏌ヨ
+const handleQuery = () => {
+  pagination.current = 1;
+  fetchListData();
+}
+
+// 閲嶇疆鏌ヨ
+const resetQuery = () => {
+  queryParams.name = '';
+  pagination.current = 1;
+  fetchListData();
+}
+
+// 鍒嗛〉澶у皬鏀瑰彉
+const handleSizeChange = (size) => {
+  pagination.size = size;
+  pagination.current = 1;
+  fetchListData();
+}
+
+// 褰撳墠椤垫敼鍙�
+const handleCurrentChange = (current) => {
+  pagination.current = current;
+  fetchListData();
+}
+
+// 鍔犺浇璁惧鍒楄〃锛堝湪鎵撳紑寮规鏃惰皟鐢級
+const loadDeviceName = async () => {
   try {
-    const res = await getSparePartsList();
-    if (res.code === 200) {
-      categories.value = res.data.records;
-    } else {
-      ElMessage.error(res.message || '鑾峰彇鍒嗙被鍒楄〃澶辫触');
-    }
+    const { data } = await getDeviceLedger();
+    deviceOptions.value = data || [];
   } catch (error) {
-    ElMessage.error('鑾峰彇鍒嗙被鍒楄〃澶辫触');
-  } finally {
-    loading.value = false;
+    ElMessage.error('鑾峰彇璁惧鍒楄〃澶辫触');
   }
 };
 
 // 鏂板鍒嗙被
-const addCategory = () => {
+const addCategory = async () => {
+  await loadDeviceName();
   form.id = '';
   form.name = '';
   form.sparePartsNo = '';
   form.status = '';
   form.description = '';
-  form.parentId = null;
+  form.deviceLedgerIds = [];
+  form.quantity = undefined;
+  form.price = null;
   operationType.value = 'add'
   dialogVisible.value = true;
-  console.log('dialogVisible 鏇存柊涓�', dialogVisible.value);
 };
 
 // 缂栬緫鍒嗙被
-const editCategory = (row) => {
+const editCategory = async (row) => {
+  await loadDeviceName();
   Object.assign(form, row);
+  // 濡傛灉鍚庣杩斿洖鐨勬槸 deviceIds 瀛楃涓诧紝闇�瑕佽浆鎹负鏁扮粍
+  if (row.deviceIds && typeof row.deviceIds === 'string') {
+    // 纭繚ID绫诲瀷涓庤澶囬�夐」涓殑ID绫诲瀷涓�鑷�
+    const deviceIdsArray = row.deviceIds.split(',').map(id => id.trim()).filter(id => id);
+    // 濡傛灉璁惧閫夐」涓殑ID鏄暟瀛楃被鍨嬶紝鍒欒浆鎹负鏁板瓧
+    if (deviceOptions.value.length > 0 && typeof deviceOptions.value[0].id === 'number') {
+      form.deviceLedgerIds = deviceIdsArray.map(id => Number(id)).filter(id => !isNaN(id));
+    } else {
+      form.deviceLedgerIds = deviceIdsArray;
+    }
+  } else if (row.deviceIds && Array.isArray(row.deviceIds)) {
+    form.deviceLedgerIds = row.deviceIds;
+  } else {
+    form.deviceLedgerIds = [];
+  }
   operationType.value = 'edit'
   dialogVisible.value = true;
 };
@@ -223,7 +354,7 @@
     const res = await delSparePart(id);
     if (res.code === 200) {
       ElMessage.success('鍒犻櫎鎴愬姛');
-      fetchTreeData();
+      fetchListData();
     } else {
       ElMessage.error(res.message || '鍒犻櫎澶辫触');
     }
@@ -242,19 +373,31 @@
   try {
     await formRef.value.validate();
     formLoading.value = true;
+    
+    // 鏋勫缓鎻愪氦鏁版嵁
+    const submitData = {
+      ...form,
+      deviceIds: form.deviceLedgerIds && form.deviceLedgerIds.length > 0 
+        ? form.deviceLedgerIds.join(',') 
+        : ''
+    };
+    
+    // 鍒犻櫎涓嶉渶瑕佺殑瀛楁
+    delete submitData.deviceLedgerIds;
+    
     if (operationType.value === 'edit') {
-      let res = await editSparePart(form);
+      let res = await editSparePart(submitData);
       if (res.code === 200) {
-      ElMessage.success('缂栬緫鎴愬姛');
-      dialogVisible.value = false;
-      fetchTreeData();
-    }
-    } else {
-      let res = await addSparePart(form);
-        if (res.code === 200) {
         ElMessage.success('缂栬緫鎴愬姛');
         dialogVisible.value = false;
-        fetchTreeData();
+        fetchListData();
+      }
+    } else {
+      let res = await addSparePart(submitData);
+      if (res.code === 200) {
+        ElMessage.success('鏂板鎴愬姛');
+        dialogVisible.value = false;
+        fetchListData();
       }
     }
   } catch (error) {
@@ -264,10 +407,9 @@
   }
 };
 
-// 缁勪欢鎸傝浇鏃惰幏鍙栧垎绫诲垪琛�
+// 缁勪欢鎸傝浇鏃惰幏鍙栧垪琛ㄦ暟鎹�
 onMounted(() => {
-  fetchCategories();
-  fetchTreeData();
+  fetchListData();
 });
 </script>
 
@@ -275,43 +417,20 @@
 .spare-part-category {
   padding: 20px;
 }
+.search_form {
+	display: flex;
+	align-items: flex-start;
+	justify-content: space-between;
+}
 .table_list {
   margin-top: unset;
 }
-.actions {
+
+.pagination-container {
+  margin-top: 20px;
   display: flex;
-  justify-content: space-between;
-  margin-bottom: 10px;
-  align-items: center;
-}
-
-/* 宓屽鏍戝舰缁撴瀯鏍峰紡 */
-.nested-tree-node {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  width: 100%;
-  padding: 0 4px;
-  height: 30px;
-  line-height: 30px;
-}
-
-.category-code {
-  color: #606266;
-  font-size: 12px;
-  margin-left: 8px;
-}
-
-.tree-actions {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  margin-left: auto;
-}
-
-/* 琛ㄦ牸鏍峰紡璋冩暣 */
-.el-table {
-  font-size: 14px;
+  justify-content: flex-end;
+  padding: 16px 0;
 }
 
 .el-table__header-wrapper th {
@@ -321,22 +440,6 @@
 
 .el-table__row:hover > td {
   background-color: #fafafa;
-}
-
-/* 宓屽鏍戝舰缁撴瀯鐗瑰畾鏍峰紡 */
-.nested-tree {
-  background-color: transparent;
-}
-
-.nested-tree .el-tree-node__content {
-  height: auto;
-  background-color: transparent;
-}
-
-/* 鎼滅储妗嗘牱寮忚皟鏁� */
-.actions .el-input {
-  margin-right: 10px;
-  width: 200px;
 }
 
 /* 鎸夐挳缁勬牱寮� */
@@ -354,64 +457,5 @@
 .nested-tree .el-tree-node__expand-icon {
   font-size: 12px;
   margin-right: 4px;
-}
-
-/* 绌虹姸鎬佹牱寮� */
-.el-table .cell:empty::before {
-  content: '-';
-  color: #c0c4cc;
-}
-
-/* 灞曞紑/鎶樺彔鍔熻兘鏍峰紡 */
-.expand-icon {
-  display: inline-block;
-  width: 20px;
-  height: 20px;
-  text-align: center;
-  line-height: 20px;
-  cursor: pointer;
-  font-size: 12px;
-  color: #909399;
-}
-
-.expand-icon.expanded {
-  color: #409eff;
-}
-
-/* 灞曞紑鍐呭鏍峰紡 */
-.expand-content {
-  padding: 10px 20px;
-}
-
-/* 瀛愮骇鍐呭鏍峰紡 */
-.child-content {
-  padding-left: 40px;
-}
-
-/* 鏃犲瓙鍒嗙被鎻愮ず鏍峰紡 */
-.no-children {
-  padding: 10px 20px;
-  color: #909399;
-  font-size: 14px;
-}
-
-/* 纭繚灞曞紑鐨勮〃鏍兼牱寮忔纭� */
-.el-table .el-table__expanded-cell {
-  background-color: #fafafa;
-}
-
-/* 灞曞紑鐨勫瓙琛ㄦ牸鏍峰紡 */
-.el-table .el-table {
-  border-top: none;
-  border-bottom: none;
-}
-
-/* 灞曞紑鐨勫瓙琛ㄦ牸鍗曞厓鏍兼牱寮� */
-.expand-content .el-table__body-wrapper .el-table__row {
-  background-color: #ffffff;
-}
-
-.expand-content .el-table__body-wrapper .el-table__row:hover > td {
-  background-color: #fafafa;
 }
 </style>
\ No newline at end of file
diff --git a/src/views/equipmentManagement/upkeep/Form/ApproveModal.vue b/src/views/equipmentManagement/upkeep/Form/ApproveModal.vue
new file mode 100644
index 0000000..c7a9329
--- /dev/null
+++ b/src/views/equipmentManagement/upkeep/Form/ApproveModal.vue
@@ -0,0 +1,144 @@
+<template>
+  <FormDialog
+    v-model="visible"
+    title="瀹氭椂浠诲姟瀹℃壒"
+    width="800px"
+    @confirm="handleSubmit"
+    @cancel="handleClose"
+    @close="handleClose"
+  >
+    <el-descriptions :column="2" border>
+      <el-descriptions-item label="浠诲姟鍚嶇О">
+        {{ detail.taskName || "-" }}
+      </el-descriptions-item>
+      <el-descriptions-item label="瑙勬牸鍨嬪彿">
+        {{ detail.deviceModel || "-" }}
+      </el-descriptions-item>
+      <el-descriptions-item label="棰戞">
+        {{ frequencyText(detail.frequencyType) }}
+      </el-descriptions-item>
+      <el-descriptions-item label="寮�濮嬫棩鏈熶笌鏃堕棿">
+        {{ detail.frequencyDetail || "-" }}
+      </el-descriptions-item>
+      <el-descriptions-item label="鐧昏浜�">
+        {{ detail.registrant || "-" }}
+      </el-descriptions-item>
+      <el-descriptions-item label="鐧昏鏃ユ湡">
+        {{ detail.registrationDate || "-" }}
+      </el-descriptions-item>
+      <el-descriptions-item label="褰撳墠鐘舵��">
+        {{ statusText(detail.status) }}
+      </el-descriptions-item>
+      <el-descriptions-item label="澶囨敞" :span="2">
+        {{ detail.remarks || "-" }}
+      </el-descriptions-item>
+    </el-descriptions>
+    <div style="margin-top: 16px">
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="瀹℃壒缁撴灉" prop="decision">
+          <el-radio-group v-model="form.decision">
+            <el-radio label="瀹℃牳閫氳繃">瀹℃牳閫氳繃</el-radio>
+            <el-radio label="瀹℃牳涓嶉�氳繃">瀹℃牳涓嶉�氳繃</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="鐩戠潱浜�" prop="supervisoryName">
+          <el-input v-model="form.supervisoryName" placeholder="璇疯緭鍏ョ洃鐫d汉" clearable style="width: 100%" />
+        </el-form-item>
+      </el-form>
+    </div>
+  </FormDialog>
+</template>
+
+<script setup>
+import { nextTick, ref } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+import { deviceMaintenanceTaskEdit } from "@/api/equipmentManagement/upkeep";
+
+defineOptions({
+  name: "瀹氭椂浠诲姟瀹℃壒寮圭獥",
+});
+
+const emits = defineEmits(["ok"]);
+
+const visible = ref(false);
+const loading = ref(false);
+const detail = ref({});
+const formRef = ref();
+const form = ref({
+  decision: undefined, // 瀹℃牳閫氳繃 / 瀹℃牳涓嶉�氳繃
+  supervisoryName: undefined, // 鐩戠潱浜�
+});
+
+const rules = {
+  decision: [{ required: true, message: "璇烽�夋嫨瀹℃壒缁撴灉", trigger: "change" }],
+  supervisoryName: [{ required: true, message: "璇烽�夋嫨鐩戠潱浜�", trigger: "change" }],
+};
+
+const statusText = (status) => status || "-";
+
+const frequencyText = (type) => {
+  const map = {
+    DAILY: "姣忔棩",
+    WEEKLY: "姣忓懆",
+    MONTHLY: "姣忔湀",
+    QUARTERLY: "瀛e害",
+  };
+  return map[type] ?? "-";
+};
+
+const open = async (row) => {
+  detail.value = { ...(row || {}) };
+  visible.value = true;
+  await nextTick();
+  form.value.decision = undefined;
+  form.value.supervisoryName = undefined;
+};
+
+const handleClose = () => {
+  visible.value = false;
+  detail.value = {};
+  form.value.decision = undefined;
+  form.value.supervisoryName = undefined;
+};
+
+const updateStatus = async (status) => {
+  loading.value = true;
+  try {
+    const payload = { ...(detail.value || {}), status, supervisoryName: form.value.supervisoryName };
+    const { code } = await deviceMaintenanceTaskEdit(payload);
+    if (code === 200) {
+      ElMessage.success("瀹℃壒鎴愬姛");
+      emits("ok");
+      handleClose();
+    }
+  } finally {
+    loading.value = false;
+  }
+};
+
+const handleSubmit = async () => {
+  if (detail.value?.status === "瀹℃牳閫氳繃") {
+    ElMessage.warning("瀹℃牳閫氳繃鍚庝笉鍙啀娆″鎵�");
+    return;
+  }
+  await formRef.value?.validate(async (valid) => {
+    if (!valid) return;
+    const isApprove = form.value.decision === "瀹℃牳閫氳繃";
+    ElMessageBox.confirm(
+      `纭瀹℃壒${isApprove ? "閫氳繃" : "涓嶉�氳繃"}锛焋,
+      "鎻愮ず",
+      {
+        confirmButtonText: "纭畾",
+        cancelButtonText: "鍙栨秷",
+        type: "warning",
+      }
+    ).then(() => updateStatus(form.value.decision));
+  });
+};
+
+defineExpose({ open });
+</script>
+
+<style scoped></style>
+
diff --git a/src/views/equipmentManagement/upkeep/Form/MaintenanceForm.vue b/src/views/equipmentManagement/upkeep/Form/MaintenanceForm.vue
deleted file mode 100644
index bc3db70..0000000
--- a/src/views/equipmentManagement/upkeep/Form/MaintenanceForm.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<template>
-  <el-form :model="form" label-width="100px">
-    <el-form-item label="瀹為檯淇濆吇浜�">
-      <el-input
-        v-model="form.maintenanceActuallyName"
-        placeholder="璇疯緭鍏ュ疄闄呬繚鍏讳汉"
-      ></el-input>
-    </el-form-item>
-    <el-form-item label="瀹為檯淇濆吇鏃ユ湡">
-      <el-date-picker
-        v-model="form.maintenanceActuallyTime"
-        placeholder="璇烽�夋嫨瀹為檯淇濆吇鏃ユ湡"
-        format="YYYY-MM-DD HH:mm:ss"
-        value-format="YYYY-MM-DD HH:mm:ss"
-        type="datetime"
-        clearable
-        style="width: 100%"
-      />
-    </el-form-item>
-    <el-form-item label="淇濆吇缁撴灉">
-      <el-select v-model="form.maintenanceResult" placeholder="璇烽�夋嫨淇濆吇缁撴灉">
-        <el-option label="瀹屽ソ" :value="1"></el-option>
-        <el-option label="缁翠慨" :value="0"></el-option>
-      </el-select>
-    </el-form-item>
-  </el-form>
-</template>
-
-<script setup>
-import useFormData from "@/hooks/useFormData";
-import dayjs from "dayjs";
-import useUserStore from "@/store/modules/user";
-
-defineOptions({
-  name: "淇濆吇琛ㄥ崟",
-});
-
-const userStore = useUserStore();
-const { form, resetForm } = useFormData({
-  maintenanceActuallyName: undefined, // 瀹為檯淇濆吇浜�
-  maintenanceActuallyTime: undefined, // 瀹為檯淇濆吇鏃ユ湡
-  maintenanceResult: undefined, // 淇濆吇缁撴灉
-});
-
-const setForm = (data) => {
-  form.maintenanceActuallyName =
-    data.maintenanceActuallyName ?? userStore.nickName;
-  form.maintenanceActuallyTime =
-    dayjs(data.maintenanceActuallyTime).format("YYYY-MM-DD HH:mm:ss") ??
-    dayjs().format("YYYY-MM-DD HH:mm:ss");
-  form.maintenanceResult = data.maintenanceResult;
-};
-
-const getForm = () => {
-  return form;
-};
-
-defineExpose({
-  getForm,
-  setForm,
-  resetForm,
-});
-</script>
-
-<style lang="scss" scoped></style>
diff --git a/src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue b/src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue
new file mode 100644
index 0000000..2951df7
--- /dev/null
+++ b/src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue
@@ -0,0 +1,146 @@
+<template>
+  <FormDialog
+    v-model="visible"
+    :title="'璁惧淇濆吇'"
+    width="500px"
+    @confirm="sendForm"
+    @cancel="handleCancel"
+    @close="handleClose"
+  >
+    <el-form :model="form" :rules="rules" label-width="120px" ref="formRef">
+      <el-form-item label="瀹為檯淇濆吇浜�" prop="maintenanceActuallyName">
+        <el-input
+          v-model="form.maintenanceActuallyName"
+          placeholder="璇疯緭鍏ュ疄闄呬繚鍏讳汉"
+        ></el-input>
+      </el-form-item>
+      <el-form-item label="瀹為檯淇濆吇鏃ユ湡" prop="maintenanceActuallyTime">
+        <el-date-picker
+          v-model="form.maintenanceActuallyTime"
+          placeholder="璇烽�夋嫨瀹為檯淇濆吇鏃ユ湡"
+          format="YYYY-MM-DD HH:mm:ss"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="datetime"
+          clearable
+          style="width: 100%"
+        />
+      </el-form-item>
+      <el-form-item label="淇濆吇鐘舵��" prop="status">
+        <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-form-item label="淇濆吇缁撴灉" prop="maintenanceResult">
+        <el-input
+          v-model="form.maintenanceResult"
+          placeholder="璇疯緭鍏ヤ繚鍏荤粨鏋�"
+          type="text" />
+      </el-form-item>
+			<el-form-item label="鏈淇濆吇閲戦" prop="maintenancePrice">
+				<el-input-number v-model="form.maintenancePrice" :min="0" :precision="2" style="width: 100%" />
+			</el-form-item>
+    </el-form>
+  </FormDialog>
+</template>
+
+<script setup>
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+import { addMaintenance } from "@/api/equipmentManagement/upkeep";
+import useFormData from "@/hooks/useFormData";
+import dayjs from "dayjs";
+import useUserStore from "@/store/modules/user";
+import { ElMessage } from "element-plus";
+
+defineOptions({
+  name: "淇濆吇妯℃�佹",
+});
+
+const emits = defineEmits(["ok"]);
+
+// 淇濆瓨璁″垝淇濆吇璁板綍鐨刬d
+const planId = ref();
+const visible = ref(false);
+const loading = ref(false);
+const formRef = ref();
+const userStore = useUserStore();
+
+const { form, resetForm } = useFormData({
+  maintenanceActuallyName: undefined, // 瀹為檯淇濆吇浜�
+  maintenanceActuallyTime: undefined, // 瀹為檯淇濆吇鏃ユ湡
+  maintenanceResult: undefined, // 淇濆吇缁撴灉
+	maintenancePrice: undefined, // 淇濆吇閲戦
+  status: 0, // 淇濆吇鐘舵��
+});
+
+const rules = {
+  maintenanceActuallyName: [
+    { required: true, message: "璇疯緭鍏ュ疄闄呬繚鍏讳汉", trigger: "blur" },
+  ],
+  maintenanceActuallyTime: [
+    { required: true, message: "璇烽�夋嫨瀹為檯淇濆吇鏃ユ湡", trigger: "change" },
+  ],
+  maintenanceResult: [
+    { required: true, message: "璇疯緭鍏ヤ繚鍏荤粨鏋�", trigger: "blur" },
+  ],
+  maintenancePrice: [
+    { required: true, message: "璇疯緭鍏ユ湰娆′繚鍏婚噾棰�", trigger: "change" },
+  ],
+};
+
+const setForm = (data) => {
+  form.maintenanceActuallyName =
+    data.maintenanceActuallyName ?? userStore.nickName;
+  form.maintenanceActuallyTime =
+    data.maintenanceActuallyTime 
+      ? dayjs(data.maintenanceActuallyTime).format("YYYY-MM-DD HH:mm:ss")
+      : dayjs().format("YYYY-MM-DD HH:mm:ss");
+  form.maintenanceResult = data.maintenanceResult;
+  form.status = 1; // 榛樿鐘舵�佷负瀹岀粨
+};
+
+/**
+ * @desc 淇濆瓨淇濆吇
+ */
+const sendForm = async () => {
+  await formRef.value?.validate(async (valid) => {
+    if (!valid) return;
+    loading.value = true;
+    try {
+      const { code } = await addMaintenance({ id: planId.value, ...form });
+      if (code == 200) {
+        ElMessage.success("淇濆吇鎴愬姛");
+        emits("ok");
+        resetForm();
+        visible.value = false;
+      }
+    } finally {
+      loading.value = false;
+    }
+  });
+};
+
+const handleCancel = () => {
+  resetForm();
+  visible.value = false;
+};
+
+const handleClose = () => {
+  resetForm();
+  visible.value = false;
+};
+
+const open = async (id, row) => {
+  planId.value = id; // 淇濆瓨璁″垝淇濆吇璁板綍鐨刬d
+  visible.value = true;
+  await nextTick();
+  setForm(row);
+};
+
+defineExpose({
+  open,
+});
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/views/equipmentManagement/upkeep/Form/PlanForm.vue b/src/views/equipmentManagement/upkeep/Form/PlanForm.vue
deleted file mode 100644
index 1d94b68..0000000
--- a/src/views/equipmentManagement/upkeep/Form/PlanForm.vue
+++ /dev/null
@@ -1,97 +0,0 @@
-<template>
-  <el-form :model="form" label-width="100px">
-    <el-form-item label="璁惧鍚嶇О">
-      <el-select
-        v-model="form.deviceLedgerId"
-        @change="setDeviceModel"
-        placeholder="璇烽�夋嫨璁惧"
-      >
-        <el-option
-          v-for="(item, index) in deviceOptions"
-          :key="index"
-          :label="item.deviceName"
-          :value="item.id"
-        ></el-option>
-      </el-select>
-    </el-form-item>
-    <el-form-item label="瑙勬牸鍨嬪彿">
-      <el-input
-        v-model="form.deviceModel"
-        placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�"
-        disabled
-      />
-    </el-form-item>
-    <el-form-item label="璁″垝淇濆吇鏃ユ湡">
-      <el-date-picker
-        style="width: 100%"
-        v-model="form.maintenancePlanTime"
-        format="YYYY-MM-DD"
-        value-format="YYYY-MM-DD HH:mm:ss"
-        type="date"
-        placeholder="璇烽�夋嫨璁″垝淇濆吇鏃ユ湡鏃ユ湡"
-        clearable
-      />
-    </el-form-item>
-  </el-form>
-</template>
-
-<script setup>
-import useFormData from "@/hooks/useFormData";
-import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
-import { onMounted } from "vue";
-import dayjs from "dayjs";
-
-defineOptions({
-  name: "璁″垝琛ㄥ崟",
-});
-
-const deviceOptions = ref([]);
-const loadDeviceName = async () => {
-  const { data } = await getDeviceLedger();
-  deviceOptions.value = data;
-};
-
-const { form, resetForm } = useFormData({
-  deviceLedgerId: undefined, // 璁惧Id
-  deviceName: undefined, // 璁惧鍚嶇О
-  deviceModel: undefined, // 瑙勬牸鍨嬪彿
-  maintenancePlanTime: undefined, // 璁″垝淇濆吇鏃ユ湡
-});
-
-const setDeviceModel = (id) => {
-  const option = deviceOptions.value.find((item) => item.id === id);
-  form.deviceModel = option.deviceModel;
-};
-
-const getForm = () => {
-  return form;
-};
-
-/**
- * @desc 璁剧疆琛ㄥ崟鍐呭
- * @param data 璁惧淇℃伅
- */
-const setForm = (data) => {
-  form.deviceLedgerId = data.deviceLedgerId;
-  form.deviceName = data.deviceName;
-  form.deviceModel = data.deviceModel;
-  form.maintenancePlanTime = dayjs(data.maintenancePlanTime).format(
-    "YYYY-MM-DD HH:mm:ss"
-  );
-};
-
-const loadForm = () => {};
-
-onMounted(() => {
-  loadDeviceName();
-});
-
-defineExpose({
-  loadForm,
-  resetForm,
-  getForm,
-  setForm,
-});
-</script>
-
-<style lang="scss" scoped></style>
diff --git a/src/views/equipmentManagement/upkeep/Form/PlanModal.vue b/src/views/equipmentManagement/upkeep/Form/PlanModal.vue
new file mode 100644
index 0000000..19095b9
--- /dev/null
+++ b/src/views/equipmentManagement/upkeep/Form/PlanModal.vue
@@ -0,0 +1,188 @@
+<template>
+  <FormDialog
+    v-model="visible"
+    :title="id ? '缂栬緫璁惧淇濆吇璁″垝' : '鏂板璁惧淇濆吇璁″垝'"
+    width="500px"
+    @confirm="sendForm"
+    @cancel="handleCancel"
+    @close="handleClose"
+  >
+    <el-form :model="form" label-width="100px">
+      <el-form-item label="璁惧鍚嶇О">
+        <el-select
+          v-model="form.deviceLedgerId"
+          @change="setDeviceModel"
+          placeholder="璇烽�夋嫨璁惧"
+          filterable
+          default-first-option
+          :reserve-keyword="false"
+        >
+          <el-option
+            v-for="(item, index) in deviceOptions"
+            :key="index"
+            :label="item.deviceName"
+            :value="item.id"
+          ></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="瑙勬牸鍨嬪彿">
+        <el-input
+          v-model="form.deviceModel"
+          placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�"
+          disabled
+        />
+      </el-form-item>
+      <el-form-item label="褰曞叆浜�">
+        <el-select
+          v-model="form.createUser"
+          placeholder="璇烽�夋嫨"
+          filterable
+          default-first-option
+          :reserve-keyword="false"
+          clearable
+        >
+          <el-option
+            v-for="item in userList"
+            :key="item.userId"
+            :label="item.nickName"
+            :value="item.userId"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item v-if="id" 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-form-item label="璁″垝淇濆吇鏃ユ湡">
+        <el-date-picker
+          style="width: 100%"
+          v-model="form.maintenancePlanTime"
+          format="YYYY-MM-DD"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="date"
+          placeholder="璇烽�夋嫨璁″垝淇濆吇鏃ユ湡鏃ユ湡"
+          clearable
+        />
+      </el-form-item>
+    </el-form>
+  </FormDialog>
+</template>
+
+<script setup>
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+import {
+  addUpkeep,
+  editUpkeep,
+  getUpkeepById,
+} from "@/api/equipmentManagement/upkeep";
+import { ElMessage } from "element-plus";
+import useFormData from "@/hooks/useFormData";
+import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
+import { onMounted } from "vue";
+import dayjs from "dayjs";
+import { userListNoPage } from "@/api/system/user.js";
+
+defineOptions({
+  name: "璁惧淇濆吇鏂板璁″垝",
+});
+
+const emits = defineEmits(["ok"]);
+
+const id = ref();
+const visible = ref(false);
+const loading = ref(false);
+
+const deviceOptions = ref([]);
+const loadDeviceName = async () => {
+  const { data } = await getDeviceLedger();
+  deviceOptions.value = data;
+};
+
+const { form, resetForm } = useFormData({
+  deviceLedgerId: undefined, // 璁惧Id
+  deviceName: undefined, // 璁惧鍚嶇О
+  deviceModel: undefined, // 瑙勬牸鍨嬪彿
+  maintenancePlanTime: undefined, // 璁″垝淇濆吇鏃ユ湡
+  createUser: undefined, // 褰曞叆浜�
+  status: 0, //淇濅慨鐘舵��
+});
+
+const setDeviceModel = (deviceId) => {
+  const option = deviceOptions.value.find((item) => item.id === deviceId);
+  form.deviceModel = option.deviceModel;
+};
+
+/**
+ * @desc 璁剧疆琛ㄥ崟鍐呭
+ * @param data 璁惧淇℃伅
+ */
+const setForm = (data) => {
+  form.deviceLedgerId = data.deviceLedgerId;
+  form.deviceName = data.deviceName;
+  form.deviceModel = data.deviceModel;
+  form.createUser = Number(data.createUser);
+  form.status = data.status;
+  form.maintenancePlanTime = dayjs(data.maintenancePlanTime).format(
+    "YYYY-MM-DD HH:mm:ss"
+  );
+};
+
+// 鐢ㄦ埛鍒楄〃
+const userList = ref([]);
+
+onMounted(() => {
+  loadDeviceName();
+  userListNoPage().then((res) => {
+    userList.value = res.data;
+  });
+});
+
+const openEdit = async (editId) => {
+  const { data } = await getUpkeepById(editId);
+  id.value = editId;
+  visible.value = true;
+  await nextTick();
+  setForm(data);
+};
+
+const sendForm = async () => {
+  loading.value = true;
+  try {
+    const { code } = id.value
+      ? await editUpkeep({ id: unref(id), ...form })
+      : await addUpkeep(form);
+    if (code == 200) {
+      ElMessage.success(`${id.value ? "缂栬緫" : "鏂板"}璁″垝鎴愬姛`);
+      visible.value = false;
+      emits("ok");
+    }
+  } finally {
+    loading.value = false;
+  }
+};
+
+const handleCancel = () => {
+  resetForm();
+  visible.value = false;
+};
+
+const handleClose = () => {
+  resetForm();
+  visible.value = false;
+};
+
+const openModal = () => {
+  id.value = undefined;
+  visible.value = true;
+};
+
+defineExpose({
+  openModal,
+  openEdit,
+});
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/views/equipmentManagement/upkeep/Form/formDia.vue b/src/views/equipmentManagement/upkeep/Form/formDia.vue
new file mode 100644
index 0000000..1a113cc
--- /dev/null
+++ b/src/views/equipmentManagement/upkeep/Form/formDia.vue
@@ -0,0 +1,337 @@
+<template>
+	<FormDialog
+		v-model="dialogVisitable"
+		:title="operationType === 'add' ? '鏂板淇濆吇浠诲姟' : '缂栬緫淇濆吇浠诲姟'"
+		width="800px"
+		:operation-type="operationType"
+		@confirm="submitForm"
+		@cancel="cancel"
+		@close="cancel"
+	>
+		<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
+			<el-row>
+				<el-col :span="12">
+					<el-form-item label="璁惧鍚嶇О" prop="taskIds">
+						<el-select v-model="form.taskIds" @change="setDeviceModel" multiple 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-row>
+			<el-row>
+				<el-col :span="12">
+					<el-form-item label="褰曞叆浜�" prop="inspector">
+						<el-select
+							v-model="form.inspector"
+							filterable
+							default-first-option
+							:reserve-keyword="false"
+							placeholder="璇烽�夋嫨"
+							clearable
+						>
+							<el-option
+								v-for="item in userList"
+								:label="item.nickName"
+								:value="item.userId"
+								:key="item.userId"
+							/>
+						</el-select>
+					</el-form-item>
+				</el-col>
+				<el-col :span="12">
+					<el-form-item label="鐧昏鏃堕棿" prop="registrationDate">
+						<el-date-picker
+							v-model="form.registrationDate"
+							type="date"
+							placeholder="閫夋嫨鐧昏鏃ユ湡"
+							format="YYYY-MM-DD"
+							value-format="YYYY-MM-DD"
+							style="width: 100%"
+						/>
+					</el-form-item>
+				</el-col>
+			</el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="瀹℃壒浜�" prop="auditName">
+            <el-select
+              v-model="form.auditName"
+              filterable
+              placeholder="璇烽�夋嫨瀹℃壒浜�"
+              clearable
+            >
+              <el-option
+                v-for="item in userList"
+                :key="item.userId"
+                :label="item.nickName"
+                :value="item.nickName"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+			<el-row>
+				<el-col :span="12">
+					<el-form-item label="浠诲姟棰戠巼" prop="frequencyType">
+						<el-select v-model="form.frequencyType" placeholder="璇烽�夋嫨" clearable>
+							<el-option label="姣忔棩" value="DAILY"/>
+							<el-option label="姣忓懆" value="WEEKLY"/>
+							<el-option label="姣忔湀" value="MONTHLY"/>
+							<el-option label="瀛e害" value="QUARTERLY"/>
+						</el-select>
+					</el-form-item>
+				</el-col>
+				<el-col :span="12" v-if="form.frequencyType === 'DAILY' && form.frequencyType">
+					<el-form-item label="鏃ユ湡" prop="frequencyDetail">
+						<el-time-picker v-model="form.frequencyDetail" placeholder="閫夋嫨鏃堕棿" format="HH:mm"
+														value-format="HH:mm" />
+					</el-form-item>
+				</el-col>
+				<el-col :span="12" v-if="form.frequencyType === 'WEEKLY' && form.frequencyType">
+					<el-form-item label="鏃ユ湡" prop="frequencyDetail">
+						<el-select v-model="form.week" placeholder="璇烽�夋嫨" clearable style="width: 50%">
+							<el-option label="鍛ㄤ竴" value="MON"/>
+							<el-option label="鍛ㄤ簩" value="TUE"/>
+							<el-option label="鍛ㄤ笁" value="WED"/>
+							<el-option label="鍛ㄥ洓" value="THU"/>
+							<el-option label="鍛ㄤ簲" value="FRI"/>
+							<el-option label="鍛ㄥ叚" value="SAT"/>
+							<el-option label="鍛ㄦ棩" value="SUN"/>
+						</el-select>
+						<el-time-picker v-model="form.time" placeholder="閫夋嫨鏃堕棿" format="HH:mm"
+														value-format="HH:mm"  style="width: 50%"/>
+					</el-form-item>
+				</el-col>
+				<el-col :span="12" v-if="form.frequencyType === 'MONTHLY' && form.frequencyType">
+					<el-form-item label="鏃ユ湡" prop="frequencyDetail">
+						<el-date-picker
+							v-model="form.frequencyDetail"
+							type="datetime"
+							clearable
+							placeholder="閫夋嫨寮�濮嬫棩鏈�"
+							format="DD,HH:mm"
+							value-format="DD,HH:mm"
+						/>
+					</el-form-item>
+				</el-col>
+				<el-col :span="12" v-if="form.frequencyType === 'QUARTERLY' && form.frequencyType">
+					<el-form-item label="鏃ユ湡" prop="frequencyDetail">
+						<el-date-picker
+							v-model="form.frequencyDetail"
+							type="datetime"
+							clearable
+							placeholder="閫夋嫨寮�濮嬫棩鏈�"
+							format="MM,DD,HH:mm"
+							value-format="MM,DD,HH:mm"
+						/>
+					</el-form-item>
+				</el-col>
+			</el-row>
+			<el-row>
+				<el-col :span="12">
+					<el-form-item label="澶囨敞" prop="remarks">
+						<el-input v-model="form.remarks" placeholder="璇疯緭鍏ュ娉�" type="textarea" />
+					</el-form-item>
+				</el-col>
+			</el-row>
+		</el-form>
+	</FormDialog>
+</template>
+
+<script setup>
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+import { reactive, ref, getCurrentInstance, toRefs } from "vue";
+import {userListNoPageByTenantId} from "@/api/system/user.js";
+import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
+import { deviceMaintenanceTaskAdd, deviceMaintenanceTaskEdit } from "@/api/equipmentManagement/upkeep";
+import { getCurrentDate } from "@/utils/index.js";
+import useUserStore from "@/store/modules/user.js";
+
+const { proxy } = getCurrentInstance()
+const emit = defineEmits()
+const dialogVisitable = ref(false);
+const operationType = ref('add');
+const deviceOptions = ref([]);
+const userStore = useUserStore();
+const data = reactive({
+	form: {
+		taskIds: [],
+		taskName: undefined,
+		// 褰曞叆浜猴細鍗曢�変竴涓敤鎴� id
+		inspector: undefined,
+    auditName: undefined,
+		remarks: '',
+		frequencyType: '',
+		frequencyDetail: '',
+		week: '',
+		time: '',
+		deviceModel: undefined, // 瑙勬牸鍨嬪彿
+		registrationDate: ''
+	},
+	rules: {
+		taskIds: [{ required: true, message: "璇烽�夋嫨璁惧", trigger: "change" },],
+		inspector: [{ required: true, message: "璇烽�夋嫨褰曞叆浜�", trigger: "blur" },],
+		registrationDate: [{ required: true, message: "璇烽�夋嫨鐧昏鏃堕棿", trigger: "change" }],
+    auditName: [{ required: true, message: "璇烽�夋嫨瀹℃壒浜�", trigger: "change" }],
+	}
+})
+const { form, rules } = toRefs(data)
+const userList = ref([])
+
+const loadDeviceName = async () => {
+	const { data } = await getDeviceLedger();
+	deviceOptions.value = data;
+};
+
+// 閫夋嫨璁惧鏃讹紝鍥炲~璁惧鍚嶇О(taskName)鍜岃鏍煎瀷鍙�(deviceModel)
+const setDeviceModel = (ids) => {
+	if (!ids || ids.length === 0) {
+		form.value.taskIds = []
+		form.value.taskName = undefined
+		form.value.deviceModel = undefined
+		return
+	}
+	
+	const selectedDevices = deviceOptions.value.filter((item) => ids.includes(item.id))
+	if (selectedDevices.length > 0) {
+		form.value.taskIds = ids
+		form.value.taskName = selectedDevices.map(d => d.deviceName).join(',')
+		form.value.deviceModel = selectedDevices.map(d => d.deviceModel || '-').join(',')
+	}
+}
+
+// 鎵撳紑寮规
+const openDialog = async (type, row) => {
+	dialogVisitable.value = true
+	operationType.value = type
+	
+	// 閲嶇疆琛ㄥ崟
+	resetForm();
+	
+	// 鍔犺浇鐢ㄦ埛鍒楄〃
+	userListNoPageByTenantId().then((res) => {
+		userList.value = res.data;
+	});
+	
+	// 鍔犺浇璁惧鍒楄〃
+	await loadDeviceName();
+	
+	if (type === 'edit' && row) {
+		form.value = { ...row }
+		// 缂栬緫鏃剁敤鎺ュ彛杩斿洖鐨� registrantId 鍥炴樉褰曞叆浜�
+		if (row.registrantId) {
+			form.value.inspector = row.registrantId
+		}
+		// 濡傛灉鏈夎澶嘔D鏁扮粍锛岃浆鎹负鏁扮粍骞惰缃澶囦俊鎭�
+		if (row.taskIds) {
+			form.value.taskIds = row.taskIds.split(',').map(id => parseInt(id.trim()))
+			setDeviceModel(form.value.taskIds)
+		}
+	} else if (type === 'add') {
+		// 鏂板鏃惰缃櫥璁版棩鏈熶负褰撳ぉ
+		form.value.registrationDate = getCurrentDate();
+		// 鏂板鏃惰缃綍鍏ヤ汉涓哄綋鍓嶇櫥褰曡处鎴�
+		form.value.inspector = userStore.id;
+	}
+}
+
+// 鍏抽棴瀵硅瘽妗�
+const cancel = () => {
+	resetForm()
+	dialogVisitable.value = false
+	emit('closeDia')
+}
+
+// 閲嶇疆琛ㄥ崟鍑芥暟
+const resetForm = () => {
+	if (proxy.$refs.formRef) {
+		proxy.$refs.formRef.resetFields()
+	}
+	// 閲嶇疆琛ㄥ崟鏁版嵁纭繚璁惧淇℃伅姝g‘閲嶇疆
+	form.value = {
+		taskIds: [],
+		taskName: undefined,
+		inspector: undefined,
+		auditName: undefined,
+		remarks: '',
+		frequencyType: '',
+		frequencyDetail: '',
+		week: '',
+		time: '',
+		deviceModel: undefined,
+		registrationDate: ''
+	}
+}
+
+// 鎻愪氦琛ㄥ崟
+const submitForm = () => {
+	proxy.$refs["formRef"].validate(async valid => {
+		if (valid) {
+			try {
+				const payload = { ...form.value }
+				// 涓嶅啀鍚戝悗绔紶淇濆吇浜哄瓧娈碉紝浠呬娇鐢ㄦ帴鍙h姹傜殑 registrant / registrantId
+				// 鏍规嵁閫夋嫨鐨�"褰曞叆浜�"璁剧疆 registrant / registrantId
+				if (payload.inspector) {
+					const selectedUser = userList.value.find(
+						(u) => String(u.userId) === String(payload.inspector)
+					)
+					if (selectedUser) {
+						payload.registrantId = selectedUser.userId
+						payload.registrant = selectedUser.nickName
+					}
+				}
+				delete payload.inspector
+				delete payload.inspectorIds
+				
+				// 澶勭悊 taskIds 鍜� taskName
+				if (payload.taskIds && Array.isArray(payload.taskIds)) {
+					payload.taskIds = payload.taskIds.join(',')
+				}
+				
+				if (payload.frequencyType === 'WEEKLY') {
+					let frequencyDetail = ''
+					frequencyDetail = payload.week + ',' + payload.time
+					payload.frequencyDetail = frequencyDetail
+				}
+				
+				// 褰曞叆鏃ユ湡锛氱洿鎺ヤ娇鐢ㄨ〃鍗曢噷鐨� registrationDate 瀛楁
+				// 涓�浜涢粯璁ょ姸鎬佸瓧娈�
+				if (payload.status === undefined || payload.status === null || payload.status === '') {
+					payload.status = '寰呭鏍�' // 榛樿鐘舵�侊細寰呭鏍�
+				}
+				payload.active = true
+				payload.deleted = 0
+				
+				if (operationType.value === 'edit') {
+					await deviceMaintenanceTaskEdit(payload)
+				} else {
+					await deviceMaintenanceTaskAdd(payload)
+				}
+				cancel()
+				proxy.$modal.msgSuccess('鎻愪氦鎴愬姛')
+			} catch (error) {
+				proxy.$modal.msgError('鎻愪氦澶辫触锛岃閲嶈瘯')
+			}
+		}
+	})
+}
+defineExpose({ openDialog })
+</script>
+
+<style scoped>
+
+</style>
diff --git a/src/views/equipmentManagement/upkeep/Modal/MaintenanceModal.vue b/src/views/equipmentManagement/upkeep/Modal/MaintenanceModal.vue
deleted file mode 100644
index 0afd512..0000000
--- a/src/views/equipmentManagement/upkeep/Modal/MaintenanceModal.vue
+++ /dev/null
@@ -1,60 +0,0 @@
-<template>
-  <el-dialog v-model="visible" :title="modalOptions.title" direction="ltr">
-    <MaintenanceForm ref="maintenanceFormRef" />
-    <template #footer>
-			<el-button type="primary" @click="sendForm" :loading="loading">
-				{{ modalOptions.confirmText }}
-			</el-button>
-      <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button>
-    </template>
-  </el-dialog>
-</template>
-
-<script setup>
-import MaintenanceForm from "../Form/MaintenanceForm.vue";
-import { useModal } from "@/hooks/useModal";
-import { addMaintenance } from "@/api/equipmentManagement/upkeep";
-
-defineOptions({
-  name: "淇濆吇妯℃�佹",
-});
-
-const maintenanceFormRef = ref();
-const emits = defineEmits(["ok"]);
-
-const {
-  id,
-  visible,
-  loading,
-  openModal,
-  modalOptions,
-  handleConfirm,
-  closeModal,
-} = useModal({ title: "璁惧淇濆吇" });
-
-/**
- * @desc 淇濆瓨淇濆吇
- */
-const sendForm = async () => {
-  loading.value = true;
-  const form = await maintenanceFormRef.value.getForm();
-  const { code } = await addMaintenance({ id: id.value, ...form });
-  if (code == 200) {
-    emits("ok");
-    maintenanceFormRef.value.resetForm();
-    closeModal();
-  }
-  loading.value = false;
-};
-
-const open = async (id, row) => {
-  openModal(id);
-  await nextTick();
-  maintenanceFormRef.value.setForm(row);
-};
-defineExpose({
-  open,
-});
-</script>
-
-<style lang="scss" scoped></style>
diff --git a/src/views/equipmentManagement/upkeep/Modal/PlanModal.vue b/src/views/equipmentManagement/upkeep/Modal/PlanModal.vue
deleted file mode 100644
index d9cf246..0000000
--- a/src/views/equipmentManagement/upkeep/Modal/PlanModal.vue
+++ /dev/null
@@ -1,76 +0,0 @@
-<template>
-  <el-dialog
-    v-model="visible"
-    :title="modalOptions.title"
-    width="30%"
-    @close="close"
-  >
-    <PlanForm ref="planFormRef"></PlanForm>
-    <template #footer>
-			<el-button type="primary" @click="sendForm" :loading="loading">
-				{{ modalOptions.confirmText }}
-			</el-button>
-      <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button>
-    </template>
-  </el-dialog>
-</template>
-
-<script setup>
-import { useModal } from "@/hooks/useModal";
-import PlanForm from "../Form/PlanForm";
-import {
-  addUpkeep,
-  editUpkeep,
-  getUpkeepById,
-} from "@/api/equipmentManagement/upkeep";
-import { ElMessage } from "element-plus";
-
-defineOptions({
-  name: "璁惧淇濆吇鏂板璁″垝",
-});
-
-const emits = defineEmits(["ok"]);
-const planFormRef = ref();
-const {
-  id,
-  visible,
-  loading,
-  openModal,
-  modalOptions,
-  handleConfirm,
-  closeModal,
-} = useModal({ title: "璁惧淇濆吇璁″垝" });
-
-const openEdit = async (id) => {
-  const { data } = await getUpkeepById(id);
-  openModal(id);
-  await nextTick();
-  await planFormRef.value.setForm(data);
-};
-
-const sendForm = async () => {
-  loading.value = true;
-  const form = await planFormRef.value.getForm();
-  const { code } = id.value
-    ? await editUpkeep({ id: unref(id), ...form })
-    : await addUpkeep(form);
-  if (code == 200) {
-    ElMessage.success(`${id ? "缂栬緫" : "鏂板"}璁″垝鎴愬姛`);
-    closeModal();
-    emits("ok");
-  }
-  loading.value = false;
-};
-
-const close = () => {
-  planFormRef.value.resetForm();
-  closeModal();
-};
-
-defineExpose({
-  openModal,
-  openEdit,
-});
-</script>
-
-<style lang="scss" scoped></style>
diff --git a/src/views/equipmentManagement/upkeep/index.vue b/src/views/equipmentManagement/upkeep/index.vue
index 5612bb6..c51395f 100644
--- a/src/views/equipmentManagement/upkeep/index.vue
+++ b/src/views/equipmentManagement/upkeep/index.vue
@@ -1,78 +1,163 @@
 <template>
   <div class="app-container">
-    <el-form :model="filters" :inline="true">
-      <el-form-item label="璁惧鍚嶇О">
-        <el-input
-            v-model="filters.deviceName"
-            style="width: 240px"
-            placeholder="璇疯緭鍏ヨ澶囧悕绉�"
-            clearable
-            :prefix-icon="Search"
-            @change="getTableData"
-        />
-      </el-form-item>
-      <el-form-item label="璁″垝淇濆吇鏃ユ湡">
-        <el-date-picker
-            v-model="filters.maintenancePlanTime"
-            type="date"
-            placeholder="璇烽�夋嫨璁″垝淇濆吇鏃ユ湡"
-            size="default"
-            @change="(date) => handleDateChange(date,2)"
-        />
-      </el-form-item>
-      <el-form-item label="瀹為檯淇濆吇鏃ユ湡">
-        <el-date-picker
-            v-model="filters.maintenanceActuallyTime"
-            type="date"
-            placeholder="璇烽�夋嫨瀹為檯淇濆吇鏃ユ湡"
-            size="default"
-            @change="(date) => handleDateChange(date,1)"
-        />
-      </el-form-item>
-      <el-form-item label="瀹為檯淇濆吇浜�">
-        <el-input
-            v-model="filters.maintenanceActuallyName"
-            style="width: 240px"
-            placeholder="璇疯緭鍏ュ疄闄呬繚鍏讳汉"
-            clearable
-            :prefix-icon="Search"
-            @change="getTableData"
-        />
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" @click="getTableData">鎼滅储</el-button>
-        <el-button @click="resetFilters">閲嶇疆</el-button>
-      </el-form-item>
-    </el-form>
-    <div class="table_list">
-      <div class="actions">
-        <el-text class="mx-1" size="large">璁惧淇濆吇</el-text>
-        <div>
-          <el-button
-            type="primary"
-            icon="Plus"
-            :disabled="multipleList.length !== 1"
-            @click="addMaintain"
-          >
-            鏂板淇濆吇
-          </el-button>
-          <el-button type="success" icon="Van" @click="addPlan">
-            鏂板璁″垝
-          </el-button>
-          <el-button @click="handleOut">
-            瀵煎嚭
-          </el-button>
-          <el-button
-            type="danger"
-            icon="Delete"
-            :disabled="multipleList.length <= 0"
-            @click="delRepairByIds(multipleList.map((item) => item.id))"
-          >
-            鎵归噺鍒犻櫎
-          </el-button>
+    <el-tabs v-model="activeTab" @tab-change="handleTabChange">
+      <!-- 瀹氭椂浠诲姟绠$悊tab -->
+      <el-tab-pane label="瀹氭椂浠诲姟绠$悊" name="scheduled">
+        <div class="search_form">
+          <el-form :model="scheduledFilters" :inline="true">
+            <el-form-item label="浠诲姟鍚嶇О">
+              <el-input
+                  v-model="scheduledFilters.taskName"
+                  style="width: 240px"
+                  placeholder="璇疯緭鍏ヤ换鍔″悕绉�"
+                  clearable
+                  :prefix-icon="Search"
+                  @change="getScheduledTableData"
+              />
+            </el-form-item>
+            <el-form-item label="浠诲姟鐘舵��">
+              <el-select v-model="scheduledFilters.status" placeholder="璇烽�夋嫨浠诲姟鐘舵��" clearable style="width: 200px">
+                <el-option label="寰呭鏍�" value="寰呭鏍�" />
+                <el-option label="瀹℃牳閫氳繃" value="瀹℃牳閫氳繃" />
+                <el-option label="瀹℃牳涓嶉�氳繃" value="瀹℃牳涓嶉�氳繃" />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" @click="getScheduledTableData">鎼滅储</el-button>
+              <el-button @click="resetScheduledFilters">閲嶇疆</el-button>
+            </el-form-item>
+          </el-form>
         </div>
-      </div>
-      <PIMTable
+        <div class="table_list">
+          <div class="actions">
+            <el-text class="mx-1" size="large">瀹氭椂浠诲姟绠$悊</el-text>
+            <div>
+              <el-button type="primary" icon="Plus" @click="addScheduledTask">
+                鏂板浠诲姟
+              </el-button>
+              <el-button
+                type="danger"
+                icon="Delete"
+                :disabled="scheduledMultipleList.length <= 0"
+                @click="delScheduledTaskByIds(scheduledMultipleList.map((item) => item.id))"
+              >
+                鎵归噺鍒犻櫎
+              </el-button>
+            </div>
+          </div>
+          <PIMTable
+            rowKey="id"
+            isSelection
+            :column="scheduledColumns"
+            :tableData="scheduledDataList"
+            :page="{
+              current: scheduledPagination.currentPage,
+              size: scheduledPagination.pageSize,
+              total: scheduledPagination.total,
+            }"
+            @selection-change="handleScheduledSelectionChange"
+            @pagination="changeScheduledPage"
+          >
+            <template #statusRef="{ row }">
+              <el-tag v-if="row.status === '寰呭鏍�'" type="warning">寰呭鏍�</el-tag>
+              <el-tag v-else-if="row.status === '瀹℃牳閫氳繃'" type="success">瀹℃牳閫氳繃</el-tag>
+              <el-tag v-else-if="row.status === '瀹℃牳涓嶉�氳繃'" type="danger">瀹℃牳涓嶉�氳繃</el-tag>
+              <span v-else>{{ row.status }}</span>
+            </template>
+            <template #operation="{ row }">
+              <el-button
+                type="primary"
+                link
+                @click="editScheduledTask(row)"
+              >
+                缂栬緫
+              </el-button>
+              <el-button
+                type="warning"
+                link
+                :disabled="row.status === '瀹℃牳閫氳繃'"
+                @click="openScheduledApprove(row)"
+              >
+                瀹℃壒
+              </el-button>
+              <el-button
+                type="danger"
+                link
+                @click="delScheduledTaskByIds(row.id)"
+              >
+                鍒犻櫎
+              </el-button>
+            </template>
+          </PIMTable>
+        </div>
+      </el-tab-pane>
+
+      <!-- 浠诲姟璁板綍tab锛堝師璁惧淇濆吇椤甸潰锛� -->
+      <el-tab-pane label="浠诲姟璁板綍" name="record">
+        <div class="search_form">
+          <el-form :model="filters" :inline="true">
+            <el-form-item label="璁惧鍚嶇О">
+              <el-input
+                  v-model="filters.deviceName"
+                  style="width: 240px"
+                  placeholder="璇疯緭鍏ヨ澶囧悕绉�"
+                  clearable
+                  :prefix-icon="Search"
+                  @change="getTableData"
+              />
+            </el-form-item>
+            <el-form-item label="璁″垝淇濆吇鏃ユ湡">
+              <el-date-picker
+                  v-model="filters.maintenancePlanTime"
+                  type="date"
+                  placeholder="璇烽�夋嫨璁″垝淇濆吇鏃ユ湡"
+                  size="default"
+                  @change="(date) => handleDateChange(date,2)"
+              />
+            </el-form-item>
+            <el-form-item label="瀹為檯淇濆吇鏃ユ湡">
+              <el-date-picker
+                  v-model="filters.maintenanceActuallyTime"
+                  type="date"
+                  placeholder="璇烽�夋嫨瀹為檯淇濆吇鏃ユ湡"
+                  size="default"
+                  @change="(date) => handleDateChange(date,1)"
+              />
+            </el-form-item>
+            <el-form-item label="瀹為檯淇濆吇浜�">
+              <el-input
+                  v-model="filters.maintenanceActuallyName"
+                  style="width: 240px"
+                  placeholder="璇疯緭鍏ュ疄闄呬繚鍏讳汉"
+                  clearable
+                  :prefix-icon="Search"
+                  @change="getTableData"
+              />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+              <el-button @click="resetFilters">閲嶇疆</el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+        <div class="table_list">
+          <div class="actions">
+            <el-text class="mx-1" size="large">浠诲姟璁板綍</el-text>
+            <div>
+              <el-button @click="handleOut">
+                瀵煎嚭
+              </el-button>
+              <el-button
+                type="danger"
+                icon="Delete"
+                :disabled="multipleList.length <= 0 || hasFinishedStatus"
+                @click="delRepairByIds(multipleList.map((item) => item.id))"
+              >
+                鎵归噺鍒犻櫎
+              </el-button>
+            </div>
+          </div>
+         <PIMTable
         rowKey="id"
         isSelection
         :column="columns"
@@ -86,132 +171,188 @@
         @pagination="changePage"
       >
         <template #maintenanceResultRef="{ row }">
-          <el-tag v-if="row.maintenanceResult === 1" type="success">
-            瀹屽ソ
-          </el-tag>
-          <el-tag v-if="row.maintenanceResult === 0" type="danger">
-            缁翠慨
-          </el-tag>
+          <div>{{ row.maintenanceResult || '-' }}</div>
         </template>
         <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="danger">寰呬繚鍏�</el-tag>
+          <el-tag v-if="row.status === 0" type="warning">寰呬繚鍏�</el-tag>
         </template>
         <template #operation="{ row }">
+          <!-- 杩欎釜鍔熻兘璺熸柊澧炰繚鍏诲姛鑳戒竴妯′竴鏍凤紝鏈夊暐鎰忎箟锛� -->
+          <!-- <el-button
+              type="primary"
+              text
+              @click="addMaintain(row)"
+          >
+            鏂板淇濆吇
+          </el-button> -->
           <el-button
             type="primary"
-            text
-            icon="editPen"
+            link
+            :disabled="row.status === 1"
             @click="editPlan(row.id)"
           >
             缂栬緫
           </el-button>
           <el-button
+            type="success"
+            link
+            :disabled="row.status === 1"
+            @click="addMaintain(row)"
+          >
+            淇濆吇
+          </el-button>
+          <el-button
             type="danger"
-            text
-            icon="delete"
+            link
+            :disabled="row.status === 1"
             @click="delRepairByIds(row.id)"
           >
             鍒犻櫎
           </el-button>
+          <el-button
+            type="primary"
+            link
+            @click="openFileDialog(row)"
+          >
+            闄勪欢
+          </el-button>
         </template>
       </PIMTable>
-    </div>
+        </div>
+      </el-tab-pane>
+    </el-tabs>
     <PlanModal ref="planModalRef" @ok="getTableData" />
     <MaintenanceModal ref="maintainModalRef" @ok="getTableData" />
+    <FormDia ref="formDiaRef" @closeDia="getScheduledTableData" />
+    <ApproveModal ref="approveModalRef" @ok="getScheduledTableData" />
+    <FileListDialog 
+      ref="fileListDialogRef"
+      v-model="fileDialogVisible"
+      :show-upload-button="true"
+      :show-delete-button="true"
+      :delete-method="handleAttachmentDelete"
+      :name-column-label="'闄勪欢鍚嶇О'"
+      :rulesRegulationsManagementId="currentMaintenanceTaskId"
+      @upload="handleAttachmentUpload" />
   </div>
 </template>
 
 <script setup>
-import { usePaginationApi } from "@/hooks/usePaginationApi";
-import { getUpkeepPage, delUpkeep } from "@/api/equipmentManagement/upkeep";
-import { onMounted, getCurrentInstance } from "vue";
-import PlanModal from "./Modal/PlanModal.vue";
-import MaintenanceModal from "./Modal/MaintenanceModal.vue";
-import dayjs from "dayjs";
-import { ElMessageBox, ElMessage } from "element-plus";
+import { ref, onMounted, reactive, getCurrentInstance, nextTick, computed } from 'vue'
+import { Search } from '@element-plus/icons-vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import PlanModal from './Form/PlanModal.vue'
+import MaintenanceModal from './Form/MaintenanceModal.vue'
+import FormDia from './Form/formDia.vue'
+import ApproveModal from './Form/ApproveModal.vue'
+import FileListDialog from '@/components/Dialog/FileListDialog.vue'
+import {
+  getUpkeepPage,
+  delUpkeep,
+  deviceMaintenanceTaskList,
+  deviceMaintenanceTaskDel,
+} from '@/api/equipmentManagement/upkeep'
+import {
+  listMaintenanceTaskFiles,
+  addMaintenanceTaskFile,
+  delMaintenanceTaskFile,
+} from '@/api/equipmentManagement/maintenanceTaskFile'
+import dayjs from 'dayjs'
 
-defineOptions({
-  name: "璁惧淇濆吇",
-});
+const { proxy } = getCurrentInstance()
 
-const { proxy } = getCurrentInstance();
+// Tab鐩稿叧
+const activeTab = ref('scheduled')
 
 // 璁″垝寮圭獥鎺у埗鍣�
-const planModalRef = ref();
+const planModalRef = ref()
 // 淇濆吇寮圭獥鎺у埗鍣�
-const maintainModalRef = ref();
+const maintainModalRef = ref()
+// 瀹氭椂浠诲姟寮圭獥鎺у埗鍣�
+const formDiaRef = ref()
+// 淇濆吇瀹℃壒寮圭獥
+const approveModalRef = ref()
+// 闄勪欢寮圭獥
+const fileListDialogRef = ref(null)
+const fileDialogVisible = ref(false)
+const currentMaintenanceTaskId = ref(null)
 
-// 琛ㄦ牸澶氶�夋閫変腑椤�
-const multipleList = ref([]);
+// 浠诲姟璁板綍tab锛堝師璁惧淇濆吇椤甸潰锛夌浉鍏冲彉閲�
+const filters = reactive({
+  deviceName: '',
+  maintenancePlanTime: '',
+  maintenanceActuallyTime: '',
+  maintenanceActuallyName: '',
+})
 
-// 澶氶�夊悗鍋氫粈涔�
-const handleSelectionChange = (selectionList) => {
-  multipleList.value = selectionList;
-};
+const dataList = ref([])
+const pagination = ref({
+  currentPage: 1,
+  pageSize: 10,
+  total: 0,
+})
+const multipleList = ref([])
 
-// 琛ㄦ牸閽╁瓙
-const {
-  filters,
-  columns,
-  dataList,
-  pagination,
-  getTableData,
-  resetFilters,
-  onCurrentChange,
-} = usePaginationApi(getUpkeepPage, {
-  deviceName: undefined,
-  maintenancePlanTime: undefined,
-  maintenanceActuallyTime: undefined,
-  maintenanceActuallyName: undefined,
-}, [
-  {
-    label: "璁惧鍚嶇О",
-    align: "center",
-    prop: "deviceName",
-  },
-  {
-    label: "瑙勬牸鍨嬪彿",
-    align: "center",
-    prop: "deviceModel",
-  },
-  {
-    label: "璁″垝淇濆吇鏃ユ湡",
-    align: "center",
-    prop: "maintenancePlanTime",
-    formatData: (cell) => dayjs(cell).format("YYYY-MM-DD"),
-  },
-  {
-    label: "褰曞叆浜�",
-    align: "center",
-    prop: "createUserName",
-  },
-  {
-    label: "褰曞叆鏃ユ湡",
-    align: "center",
-    prop: "createTime",
-    formatData: (cell) => dayjs(cell).format("YYYY-MM-DD HH:mm:ss"),
-    width: 200,
-  },
-  {
-    label: "瀹為檯淇濆吇浜�",
-    align: "center",
-    prop: "maintenanceActuallyName",
-  },
-  {
-    label: "瀹為檯淇濆吇鏃ユ湡",
-    align: "center",
-    prop: "maintenanceActuallyTime",
-    formatData: (cell) =>
-      cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-",
-  },
-  {
-    label: "淇濆吇缁撴灉",
-    align: "center",
-    prop: "maintenanceResult",
-    dataType: "slot",
-    slot: "maintenanceResultRef",
-  },
+// 瀹氭椂浠诲姟绠$悊tab鐩稿叧鍙橀噺
+const scheduledFilters = reactive({
+  taskName: '',
+  status: '',
+})
+
+const scheduledDataList = ref([])
+const scheduledPagination = reactive({
+  currentPage: 1,
+  pageSize: 10,
+  total: 0,
+})
+const scheduledMultipleList = ref([])
+
+// 瀹氭椂浠诲姟绠$悊琛ㄦ牸鍒楅厤缃�
+const scheduledColumns = ref([
+	{ prop: "taskName", label: "璁惧鍚嶇О"},
+	{
+		label: "瑙勬牸鍨嬪彿",
+		prop: "deviceModel",
+	},
+	{
+		prop: "frequencyType",
+		label: "棰戞",
+		minWidth: 150,
+		// PIMTable 浣跨敤鐨勬槸 formatData锛岃�屼笉鏄� Element-Plus 鐨� formatter
+		formatData: (cell) => ({
+			DAILY: "姣忔棩",
+			WEEKLY: "姣忓懆",
+			MONTHLY: "姣忔湀",
+			QUARTERLY: "瀛e害"
+		}[cell] || "")
+	},
+	{
+		prop: "frequencyDetail",
+		label: "寮�濮嬫棩鏈熶笌鏃堕棿",
+		minWidth: 150,
+		// 鍚屾牱鏀圭敤 formatData锛孭IMTable 鍐呴儴浼氭妸鍗曞厓鏍煎�间紶杩涙潵
+		formatData: (cell) => {
+			if (typeof cell !== 'string') return '';
+			let val = cell;
+			const replacements = {
+				MON: '鍛ㄤ竴',
+				TUE: '鍛ㄤ簩',
+				WED: '鍛ㄤ笁',
+				THU: '鍛ㄥ洓',
+				FRI: '鍛ㄤ簲',
+				SAT: '鍛ㄥ叚',
+				SUN: '鍛ㄦ棩'
+			};
+			// 浣跨敤姝e垯涓�娆℃�ф浛鎹㈡墍鏈夊尮閰嶉」
+			return val.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => replacements[match]);
+		}
+	},
+	{ prop: "registrant", label: "鐧昏浜�", minWidth: 100 },
+	{ prop: "registrationDate", label: "鐧昏鏃ユ湡", minWidth: 100 },
+	{ prop: "auditName", label: "瀹℃牳浜�", width: 120 },
+	{ prop: "supervisoryName", label: "鐩戠潱浜�", width: 120 },
   {
     label: "鐘舵��",
     align: "center",
@@ -219,86 +360,350 @@
     dataType: "slot",
     slot: "statusRef",
   },
-  {
-    fixed: "right",
-    label: "鎿嶄綔",
-    dataType: "slot",
-    slot: "operation",
-    align: "center",
-    width: "200px",
-  },
-]);
-// type == 1瀹為檯淇濆吇鏃堕棿 2璁″垝淇濆吇鏃堕棿
-const handleDateChange = (value,type) => {
-  filters.maintenanceActuallyTimeReq = null
-  filters.maintenancePlanTimeReq = null
-  if(type === 1){
-    if (value) {
-      filters.maintenanceActuallyTimeReq = dayjs(value).format("YYYY-MM-DD");
-    }
-  }else{
-    if (value) {
-      filters.maintenancePlanTimeReq = dayjs(value).format("YYYY-MM-DD");
-    }
+	{
+		fixed: "right",
+		label: "鎿嶄綔",
+		dataType: "slot",
+		slot: "operation",
+		align: "center",
+		width: "200px",
+	},
+])
+
+// 浠诲姟璁板綍琛ㄦ牸鍒楅厤缃紙鍘熻澶囦繚鍏昏〃鏍煎垪锛�
+const columns = ref([
+	{
+		label: "璁惧鍚嶇О",
+		align: "center",
+		prop: "deviceName",
+	},
+	{
+		label: "瑙勬牸鍨嬪彿",
+		align: "center",
+		prop: "deviceModel",
+	},
+	{
+		label: "璁″垝淇濆吇鏃ユ湡",
+		align: "center",
+		prop: "maintenancePlanTime",
+		formatData: (cell) => dayjs(cell).format("YYYY-MM-DD"),
+	},
+	{
+		label: "褰曞叆浜�",
+		align: "center",
+		prop: "createUserName",
+	},
+	// {
+	//   label: "褰曞叆鏃ユ湡",
+	//   align: "center",
+	//   prop: "createTime",
+	//   formatData: (cell) => dayjs(cell).format("YYYY-MM-DD HH:mm:ss"),
+	//   width: 200,
+	// },
+	{
+		label: "瀹為檯淇濆吇浜�",
+		align: "center",
+		prop: "maintenanceActuallyName",
+	},
+	{
+		label: "瀹為檯淇濆吇鏃ユ湡",
+		align: "center",
+		prop: "maintenanceActuallyTime",
+		formatData: (cell) =>
+			cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-",
+	},
+	{
+		label: "淇濆吇缁撴灉",
+		align: "center",
+		prop: "maintenanceResult",
+		dataType: "slot",
+		slot: "maintenanceResultRef",
+	},
+	{
+		label: "鐘舵��",
+		align: "center",
+		prop: "status",
+		dataType: "slot",
+		slot: "statusRef",
+	},
+	{
+		fixed: "right",
+		label: "鎿嶄綔",
+		dataType: "slot",
+		slot: "operation",
+		align: "center",
+		width: "350px",
+	},
+])
+
+// Tab鍒囨崲澶勭悊
+const handleTabChange = (tabName) => {
+  if (tabName === 'record') {
+    getTableData()
+  } else if (tabName === 'scheduled') {
+    getScheduledTableData()
   }
-  getTableData();
-};
+}
 
-// 鏂板淇濆吇
-const addMaintain = () => {
-  const row = multipleList.value[0];
-  maintainModalRef.value.open(row.id, row);
-};
-
-// 鏂板璁″垝
-const addPlan = () => {
-  planModalRef.value.openModal();
-};
-
-// 缂栬緫璁″垝
-const editPlan = (id) => {
-  planModalRef.value.openEdit(id);
-};
-
-const changePage = ({ page, limit }) => {
-	pagination.currentPage = page;
-	pagination.pageSize = limit;
-	onCurrentChange(page);
-};
-
-// 鍗曡鍒犻櫎
-const delRepairByIds = async (ids) => {
-  ElMessageBox.confirm("纭鍒犻櫎鎶ヤ慨鏁版嵁, 姝ゆ搷浣滀笉鍙��?", "璀﹀憡", {
-    confirmButtonText: "纭畾",
-    cancelButtonText: "鍙栨秷",
-    type: "warning",
-  }).then(async () => {
-    const { code } = await delUpkeep(ids);
-    if (code === 200) {
-      ElMessage.success("鍒犻櫎鎴愬姛");
-      getTableData();
+// 瀹氭椂浠诲姟绠$悊鐩稿叧鏂规硶
+const getScheduledTableData = async () => {
+  try {
+    const params = {
+      current: scheduledPagination.currentPage,
+      size: scheduledPagination.pageSize,
+      taskName: scheduledFilters.taskName || undefined,
+      status: scheduledFilters.status || undefined,
     }
-  });
-};
+    const { code, data } = await deviceMaintenanceTaskList(params)
+    if (code === 200) {
+      scheduledDataList.value = data?.records || []
+      scheduledPagination.total = data?.total || 0
+    }
+  } catch (error) {
+    ElMessage.error('鑾峰彇瀹氭椂浠诲姟鍒楄〃澶辫触')
+  }
+}
 
-// 瀵煎嚭
+const resetScheduledFilters = () => {
+  scheduledFilters.taskName = ''
+  scheduledFilters.status = ''
+  getScheduledTableData()
+}
+
+const handleScheduledSelectionChange = (selection) => {
+  scheduledMultipleList.value = selection
+}
+
+const changeScheduledPage = (page) => {
+  scheduledPagination.currentPage = page.page
+  scheduledPagination.pageSize = page.limit
+  getScheduledTableData()
+}
+
+const addScheduledTask = () => {
+  nextTick(() => {
+		formDiaRef.value?.openDialog('add');
+	});
+}
+
+const editScheduledTask = (row) => {
+  if (row) {
+		nextTick(() => {
+			formDiaRef.value?.openDialog('edit', row);
+		});
+  }
+}
+
+const delScheduledTaskByIds = async (ids) => {
+  try {
+    await ElMessageBox.confirm('纭畾鍒犻櫎閫変腑鐨勫畾鏃朵换鍔″悧锛�', '鎻愮ず', {
+      type: 'warning',
+    })
+    const payload = Array.isArray(ids) ? ids : [ids]
+    await deviceMaintenanceTaskDel(payload)
+    ElMessage.success('鍒犻櫎瀹氭椂浠诲姟鎴愬姛')
+    getScheduledTableData()
+  } catch (error) {
+    // 鐢ㄦ埛鍙栨秷鍒犻櫎
+  }
+}
+
+const handleScheduledOut = () => {
+  ElMessage.info('瀵煎嚭瀹氭椂浠诲姟鍔熻兘寰呭疄鐜�')
+}
+
+// 浠诲姟璁板綍鐩稿叧鏂规硶锛堝師璁惧淇濆吇椤甸潰鏂规硶锛�
+const getTableData = async () => {
+  try {
+    const params = {
+      current: pagination.value.currentPage,
+      size: pagination.value.pageSize,
+      deviceName: filters.deviceName || undefined,
+      maintenancePlanTime: filters.maintenancePlanTime ? dayjs(filters.maintenancePlanTime).format('YYYY-MM-DD') : undefined,
+      maintenanceActuallyTime: filters.maintenanceActuallyTime ? dayjs(filters.maintenanceActuallyTime).format('YYYY-MM-DD') : undefined,
+      maintenanceActuallyName: filters.maintenanceActuallyName || undefined,
+    }
+
+    const { code, data } = await getUpkeepPage(params)
+    if (code === 200) {
+      dataList.value = data.records
+      pagination.value.total = data.total
+    }
+  } catch (error) {
+    console.log(error);
+    
+  }
+}
+
+const resetFilters = () => {
+  filters.deviceName = ''
+  filters.maintenancePlanTime = ''
+  filters.maintenanceActuallyTime = ''
+  filters.maintenanceActuallyName = ''
+  getTableData()
+}
+
+const handleSelectionChange = (selection) => {
+  multipleList.value = selection
+}
+
+// 妫�鏌ラ�変腑鐨勮褰曚腑鏄惁鏈夊畬缁撶姸鎬佺殑
+const hasFinishedStatus = computed(() => {
+  return multipleList.value.some(item => item.status === 1)
+})
+
+const changePage = (page) => {
+  pagination.value.currentPage = page.page
+  pagination.value.pageSize = page.limit
+  getTableData()
+}
+
+const addMaintain = (row) => {
+  maintainModalRef.value.open(row.id, row)
+}
+
+// 瀹氭椂浠诲姟瀹℃壒
+const openScheduledApprove = (row) => {
+  approveModalRef.value.open(row)
+}
+
+const addPlan = () => {
+  planModalRef.value.openModal()
+}
+
+const editPlan = (id) => {
+  planModalRef.value.openEdit(id)
+}
+
+const delRepairByIds = async (ids) => {
+  // 妫�鏌ユ槸鍚︽湁瀹岀粨鐘舵�佺殑璁板綍
+  const hasFinished = multipleList.value.some(item => item.status === 1)
+  if (hasFinished) {
+    ElMessage.warning('涓嶈兘鍒犻櫎鐘舵�佷负瀹岀粨鐨勮褰�')
+    return
+  }
+  
+  try {
+    await ElMessageBox.confirm('纭鍒犻櫎淇濆吇鏁版嵁, 姝ゆ搷浣滀笉鍙��?', '璀﹀憡', {
+      confirmButtonText: '纭畾',
+      cancelButtonText: '鍙栨秷',
+      type: 'warning',
+    })
+    
+    const { code } = await delUpkeep(ids)
+    if (code === 200) {
+      ElMessage.success('鍒犻櫎鎴愬姛')
+      getTableData()
+    }
+  } catch (error) {
+    // 鐢ㄦ埛鍙栨秷鍒犻櫎
+  }
+}
+
 const handleOut = () => {
-  ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
-    confirmButtonText: "纭",
-    cancelButtonText: "鍙栨秷",
-    type: "warning",
+  ElMessageBox.confirm('閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�', '瀵煎嚭', {
+    confirmButtonText: '纭',
+    cancelButtonText: '鍙栨秷',
+    type: 'warning',
   })
     .then(() => {
-      proxy.download("/device/maintenance/export", {}, "璁惧淇濆吇.xlsx");
+      proxy.download('/device/maintenance/export', {}, '璁惧淇濆吇.xlsx')
     })
     .catch(() => {
-      ElMessage.info("宸插彇娑�");
-    });
-};
+      ElMessage.info('宸插彇娑�')
+    })
+}
+
+const handleDateChange = (date, type) => {
+  if (type === 1) {
+    filters.maintenanceActuallyTime = date ? dayjs(date).format('YYYY-MM-DD') : ''
+  } else {
+    filters.maintenancePlanTime = date ? dayjs(date).format('YYYY-MM-DD') : ''
+  }
+  getTableData()
+}
+
+// 闄勪欢鐩稿叧鏂规硶
+// 鏌ヨ闄勪欢鍒楄〃
+const fetchMaintenanceTaskFiles = async (deviceMaintenanceId) => {
+  try {
+    const params = {
+      current: 1,
+      size: 100,
+      deviceMaintenanceId,
+      rulesRegulationsManagementId:deviceMaintenanceId
+    }
+    const res = await listMaintenanceTaskFiles(params)
+    const records = res?.data?.records || []
+    const mapped = records.map(item => ({
+      id: item.id,
+      name: item.fileName || item.name,
+      url: item.fileUrl || item.url,
+      raw: item,
+    }))
+    fileListDialogRef.value?.setList(mapped)
+  } catch (error) {
+    ElMessage.error('鑾峰彇闄勪欢鍒楄〃澶辫触')
+  }
+}
+
+// 鎵撳紑闄勪欢寮圭獥
+const openFileDialog = async (row) => {
+  currentMaintenanceTaskId.value = row.id
+  fileDialogVisible.value = true
+  await fetchMaintenanceTaskFiles(row.id)
+}
+
+// 鍒锋柊闄勪欢鍒楄〃
+const refreshFileList = async () => {
+  if (!currentMaintenanceTaskId.value) return
+  await fetchMaintenanceTaskFiles(currentMaintenanceTaskId.value)
+}
+
+// 涓婁紶闄勪欢
+const handleAttachmentUpload = async (filePayload) => {
+  if (!currentMaintenanceTaskId.value) return
+  try {
+    const payload = {
+      name: filePayload?.fileName || filePayload?.name,
+      url: filePayload?.fileUrl || filePayload?.url,
+      deviceMaintenanceId: currentMaintenanceTaskId.value,
+    }
+    await addMaintenanceTaskFile(payload)
+    ElMessage.success('鏂囦欢涓婁紶鎴愬姛')
+    await refreshFileList()
+  } catch (error) {
+    ElMessage.error('鏂囦欢涓婁紶澶辫触')
+  }
+}
+
+// 鍒犻櫎闄勪欢
+const handleAttachmentDelete = async (row) => {
+  if (!row?.id) return false
+  try {
+    await ElMessageBox.confirm('纭鍒犻櫎璇ラ檮浠讹紵', '鎻愮ず', { type: 'warning' })
+  } catch {
+    return false
+  }
+  try {
+    await delMaintenanceTaskFile(row.id)
+    ElMessage.success('鍒犻櫎鎴愬姛')
+    await refreshFileList()
+    return true
+  } catch (error) {
+    ElMessage.error('鍒犻櫎澶辫触')
+    return false
+  }
+}
 
 onMounted(() => {
-  getTableData();
-});
+  // 鏍规嵁榛樿婵�娲荤殑 Tab 璋冪敤瀵瑰簲鐨勬煡璇㈡帴鍙�
+  if (activeTab.value === 'scheduled') {
+    getScheduledTableData()
+  } else {
+    getTableData()
+  }
+})
 </script>
 
 <style lang="scss" scoped>
@@ -311,3 +716,8 @@
   margin-bottom: 10px;
 }
 </style>
+
+
+
+
+

--
Gitblit v1.9.3