gaoluyang
2 天以前 d50f2acc73dc53a334c5feeb25a7e60e3df91f81
酒泉
1.设备功能迁移
已添加9个文件
已修改19个文件
已删除7个文件
4905 ■■■■■ 文件已修改
src/api/equipmentManagement/calibration.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/maintenanceTaskFile.js 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/repair.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/upkeep.js 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Dialog/FileListDialog.vue 328 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Dialog/FormDialog.vue 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/amountSummary/index.vue 180 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/calibration/index.vue 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/defectManagement/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/inspectionManagement/components/formDia.vue 397 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue 78 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/inspectionManagement/index.vue 517 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/ledger/Form.vue 118 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/ledger/Modal.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/ledger/index.vue 176 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/measurementEquipment/components/formDia.vue 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/measurementEquipment/index.vue 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/Form/MaintainForm.vue 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/Form/RepairForm.vue 180 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/Modal/ApproveModal.vue 142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/Modal/MaintainModal.vue 127 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/Modal/RepairModal.vue 239 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/index.vue 298 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/spareParts/index.vue 149 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/ApproveModal.vue 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/MaintenanceForm.vue 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/PlanForm.vue 137 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/PlanModal.vue 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/formDia.vue 337 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Modal/MaintenanceModal.vue 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Modal/PlanModal.vue 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Modal/formDia.vue 304 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/index.vue 208 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/calibration.js
@@ -24,12 +24,4 @@
    method: "post",
    data: query,
  });
}
// åˆ é™¤è®°å½•
export function ledgerRecordDelete(ids) {
  return request({
    url: "/measuringInstrumentLedgerRecord/delete",
    method: "delete",
    data: ids,
  });
}
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],
  });
}
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,
  });
};
src/api/equipmentManagement/upkeep.js
@@ -102,3 +102,20 @@
    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,
  });
};
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) {
      // å¦‚果提供了自定义上传方法,由父组件负责更新列表(通过 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,由父组件负责刷新列表,不在这里删除
    } 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>
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>
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>
src/views/equipmentManagement/calibration/index.vue
@@ -57,10 +57,10 @@
<script setup>
import {onMounted, ref} from "vue";
import {ElMessageBox, ElMessage} from "element-plus";
import {ElMessageBox} from "element-plus";
import useUserStore from "@/store/modules/user.js";
import CalibrationDia from "@/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue";
import {ledgerRecordListPage, ledgerRecordDelete} from "@/api/equipmentManagement/calibration.js";
import {ledgerRecordListPage} from "@/api/equipmentManagement/calibration.js";
const { proxy } = getCurrentInstance();
const userStore = useUserStore()
@@ -134,7 +134,6 @@
    {
        dataType: "action",
        label: "操作",
        width: 140,
        align: "center",
        fixed: 'right',
        operation: [
@@ -144,16 +143,9 @@
                clickFun: (row) => {
                    openCalibrationDia("edit", row);
                },
            },
            {
                name: "删除",
                type: "text",
                style: {
                    color: "#F56C6C"
                },
                clickFun: (row) => {
                    handleDelete(row);
                },
                disabled: (row) => {
                    return row.userId !== userStore.id
                }
            },
        ],
    },
@@ -201,26 +193,6 @@
        calibrationDia.value?.openDialog(type, row)
    })
}
// åˆ é™¤è®°å½•
const handleDelete = (row) => {
    ElMessageBox.confirm(`确认删除计量器具编号为"${row.code}"的检定记录吗?`, "删除确认", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            ledgerRecordDelete([row.id]).then(() => {
                ElMessage.success("删除成功");
                getList();
            }).catch(() => {
                ElMessage.error("删除失败");
            });
        })
        .catch(() => {
            proxy.$modal.msg("已取消删除");
        });
};
// å¯¼å‡º
const handleOut = () => {
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"
src/views/equipmentManagement/inspectionManagement/components/formDia.vue
@@ -1,116 +1,102 @@
<template>
    <div>
        <el-dialog :title="operationType === 'add' ? '新增巡检任务' : '编辑巡检任务'"
                             v-model="dialogVisitable" width="800px" @close="cancel">
            <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" 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="巡检人" prop="inspector">
                            <el-select v-model="form.inspector"                 filterable
                                                 default-first-option
                                                 :reserve-keyword="false" placeholder="请选择" multiple 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-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-col :span="12">
                        <el-form-item label="登记时间" prop="dateStr">
                            <el-date-picker
                                v-model="form.dateStr"
                                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="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="季度" 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-form>
            <template #footer>
                <div class="dialog-footer">
                    <el-button @click="cancel">取消</el-button>
                    <el-button type="primary" @click="submitForm">保存</el-button>
                </div>
            </template>
        </el-dialog>
    </div>
  <div>
    <el-dialog :title="operationType === 'add' ? '新增巡检任务' : '编辑巡检任务'"
               v-model="dialogVisitable" width="800px" @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="巡检人" prop="inspector">
              <el-select v-model="form.inspector" placeholder="请选择" multiple 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-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-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="季度" 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-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">保存</el-button>
          <el-button @click="cancel">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
@@ -127,117 +113,128 @@
const operationType = ref('add');
const deviceOptions = ref([]);
const data = reactive({
    form: {
        taskId: undefined,
        taskName: undefined,
        inspector: '',
        inspectorIds: '',
        remarks: '',
        frequencyType: '',
        frequencyDetail: '',
        week: '',
        time: '',
        dateStr: ''
    },
    rules: {
        taskId: [{ required: true, message: "请选择设备", trigger: "change" },],
        inspector: [{ required: true, message: "请输入巡检人", trigger: "blur" },],
        dateStr: [{ required: true, message: "请选择登记时间", trigger: "change" }]
    }
  form: {
    taskIds: [],
    taskName: undefined,
    inspector: '',
    inspectorIds: '',
    remarks: '',
    frequencyType: '',
    frequencyDetail: '',
    week: '',
    time: ''
  },
  rules: {
    taskIds: [{ required: true, message: "请选择设备", trigger: "change" },],
    inspector: [{ required: true, message: "请输入巡检人", trigger: "blur" },],
  }
})
const { form, rules } = toRefs(data)
const userList = ref([])
const loadDeviceName = async () => {
    const { data } = await getDeviceLedger();
    deviceOptions.value = data;
  const { data } = await getDeviceLedger();
  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(',')
  }
}
// æ‰“开弹框
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}
        form.value.inspector = form.value.inspectorIds.split(',').map(Number)
        // å¦‚果有设备ID,自动设置设备信息
        if (form.value.taskId) {
            setDeviceModel(form.value.taskId);
        }
    }
  dialogVisitable.value = true
  operationType.value = type
  // é‡ç½®è¡¨å•
  resetForm();
  // åŠ è½½ç”¨æˆ·åˆ—è¡¨
  userListNoPageByTenantId().then((res) => {
    userList.value = res.data;
  });
  // åŠ è½½è®¾å¤‡åˆ—è¡¨
  await loadDeviceName();
  if (type === 'edit' && row) {
    form.value = {...row}
    form.value.inspector = form.value.inspectorIds.split(',').map(Number)
    // å¦‚果有设备ID数组,转换为数组并设置设备信息
    if (row.taskIds) {
      form.value.taskIds = row.taskIds.split(',').map(id => parseInt(id.trim()))
      setDeviceModel(form.value.taskIds)
    }
  }
}
// å…³é—­å¯¹è¯æ¡†
const cancel = () => {
    resetForm()
    dialogVisitable.value = false
    emit('closeDia')
  resetForm()
  dialogVisitable.value = false
  emit('closeDia')
}
// é‡ç½®è¡¨å•函数
const resetForm = () => {
    if (proxy.$refs.formRef) {
        proxy.$refs.formRef.resetFields()
    }
    // é‡ç½®è¡¨å•数据确保设备信息正确重置
    form.value = {
        taskId: undefined,
        taskName: undefined,
        inspector: '',
        inspectorIds: '',
        remarks: '',
        frequencyType: '',
        frequencyDetail: '',
        week: '',
        time: ''
    }
  if (proxy.$refs.formRef) {
    proxy.$refs.formRef.resetFields()
  }
  // é‡ç½®è¡¨å•数据确保设备信息正确重置
  form.value = {
    taskIds: [],
    taskName: undefined,
    inspector: '',
    inspectorIds: '',
    remarks: '',
    frequencyType: '',
    frequencyDetail: '',
    week: '',
    time: ''
  }
}
// æäº¤è¡¨å•
const submitForm = () => {
    proxy.$refs["formRef"].validate(async valid => {
        if (valid) {
            try {
                form.value.inspectorIds = form.value.inspector.join(',')
                delete form.value.inspector
                if (form.value.frequencyType === 'WEEKLY') {
                    let frequencyDetail = ''
                    frequencyDetail = form.value.week + ',' + form.value.time
                    form.value.frequencyDetail = frequencyDetail
                }
                let res = await userStore.getInfo()
                form.value.registrantId = res.user.userId
                await addOrEditTimingTask(form.value)
                cancel()
                proxy.$modal.msgSuccess('提交成功')
            } catch (error) {
                proxy.$modal.msgError('提交失败,请重试')
            }
        }
    })
  proxy.$refs["formRef"].validate(async valid => {
    if (valid) {
      try {
        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
          form.value.frequencyDetail = frequencyDetail
        }
        let res = await userStore.getInfo()
        form.value.registrantId = res.user.userId
        await addOrEditTimingTask(form.value)
        cancel()
        proxy.$modal.msgSuccess('提交成功')
      } catch (error) {
        proxy.$modal.msgError('提交失败,请重试')
      }
    }
  })
}
defineExpose({ openDialog })
</script>
src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
@@ -32,7 +32,7 @@
        
        <!-- ç”Ÿäº§åŽ -->
        <div class="form-container">
          <div class="title">生产中</div>
          <div class="title">生产后</div>
          
          <!-- å›¾ç‰‡åˆ—表 -->
          <div style="display: flex; flex-wrap: wrap;">
@@ -59,7 +59,7 @@
        
        <!-- ç”Ÿäº§é—®é¢˜ -->
        <div class="form-container">
          <div class="title">生产后</div>
          <div class="title">生产问题</div>
          
          <!-- å›¾ç‰‡åˆ—表 -->
          <div style="display: flex; flex-wrap: wrap;">
@@ -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 å…³é”®å­—的位置,从那里开始提取相对路径
    const uploadsIndex = fileUrl.toLowerCase().indexOf('uploads');
    if (uploadsIndex > -1) {
      // ä»Ž uploads å¼€å§‹æå–路径,并将反斜杠替换为正斜杠
      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);
  // ä½¿ç”¨æ­£ç¡®çš„字段名:commonFileListBefore, 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;
src/views/equipmentManagement/inspectionManagement/index.vue
@@ -1,78 +1,77 @@
<template>
    <div class="app-container">
        <el-form :inline="true" :model="queryParams" class="search-form">
            <el-form-item label="搜索">
                <el-input
                    v-model="queryParams.searchAll"
                    placeholder="请输入关键字"
                    clearable
                    :style="{ width: '100%' }"
                />
            </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>
        <el-card>
            <div style="display: flex;flex-direction: row;justify-content: space-between;margin-bottom: 10px;">
                <el-radio-group v-model="activeRadio" @change="radioChange">
                    <el-radio-button v-for="tab in radios"
                                                     :key="tab.name"
                                                     :label="tab.label"
                                                     :value="tab.name"/>
                </el-radio-group>
                <!-- æ“ä½œæŒ‰é’®åŒº -->
                <el-space v-if="activeRadio !== 'task'">
                    <el-button type="primary" :icon="Plus" @click="handleAdd(undefined)">新建</el-button>
                    <el-button type="danger" :icon="Delete" @click="handleDelete">删除</el-button>
                    <el-button @click="handleOut">导出</el-button>
                </el-space>
                <el-space v-else>
                    <el-button @click="handleOut">导出</el-button>
                </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)' }"
                                        :page="{
          current: pageNum,
          size: pageSize,
          total: total,
        }"
                                        @pagination="pagination"
                    >
                        <template #inspector="{ row }">
                            <div class="person-tags">
                                <!-- è°ƒè¯•信息,上线时删除 -->
                                <!-- {{ console.log('inspector data:', row.inspector) }} -->
                                <template v-if="row.inspector && row.inspector.length > 0">
                                    <el-tag
                                        v-for="(person, index) in row.inspector"
                                        :key="index"
                                        size="small"
                                        type="primary"
                                        class="person-tag"
                                    >
                                        {{ person }}
                                    </el-tag>
                                </template>
                                <span v-else class="no-data">--</span>
                            </div>
                        </template>
                    </PIMTable>
                </div>
            </div>
        </el-card>
        <form-dia ref="formDia" @closeDia="handleQuery"></form-dia>
        <view-files ref="viewFiles"></view-files>
    </div>
  <div class="app-container">
    <el-form :inline="true" :model="queryParams" class="search-form">
      <el-form-item label="搜索">
        <el-input
            v-model="queryParams.searchAll"
            placeholder="请输入关键字"
            clearable
            :style="{ width: '100%' }"
        />
      </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>
    <el-card>
      <div style="display: flex;flex-direction: row;justify-content: space-between;margin-bottom: 10px;">
        <el-radio-group v-model="activeRadio" @change="radioChange">
          <el-radio-button v-for="tab in radios"
                           :key="tab.name"
                           :label="tab.label"
                           :value="tab.name"/>
        </el-radio-group>
        <!-- æ“ä½œæŒ‰é’®åŒº -->
        <el-space v-if="activeRadio !== 'task'">
          <el-button type="primary" :icon="Plus" @click="handleAdd(undefined)">新建</el-button>
          <el-button type="danger" :icon="Delete" @click="handleDelete">删除</el-button>
          <el-button @click="handleOut">导出</el-button>
        </el-space>
        <el-space v-else>
          <el-button @click="handleOut">导出</el-button>
        </el-space>
      </div>
      <div>
        <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">
              <!-- è°ƒè¯•信息,上线时删除 -->
              <!-- {{ console.log('inspector data:', row.inspector) }} -->
              <template v-if="row.inspector && row.inspector.length > 0">
                <el-tag
                  v-for="(person, index) in row.inspector"
                  :key="index"
                  size="small"
                  type="primary"
                  class="person-tag"
                >
                  {{ person }}
                </el-tag>
              </template>
              <span v-else class="no-data">--</span>
            </div>
          </template>
        </PIMTable>
      </div>
    </el-card>
    <form-dia ref="formDia" @closeDia="handleQuery"></form-dia>
    <view-files ref="viewFiles"></view-files>
  </div>
</template>
<script setup>
@@ -81,16 +80,15 @@
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";
// æŽ¥å£å¼•å…¥
import {
    delTimingTask,
    inspectionTaskList,
    timingTaskList
  delTimingTask,
  inspectionTaskList,
  timingTaskList
} from "@/api/inspectionManagement/index.js";
// å…¨å±€å˜é‡
@@ -100,14 +98,14 @@
// æŸ¥è¯¢å‚æ•°
const queryParams = reactive({
    searchAll: "",
  searchAll: "",
});
// å•选框配置
const activeRadio = ref("taskManage");
const radios = reactive([
    { name: "taskManage", label: "定时任务管理" },
    { name: "task", label: "定时任务记录" },
  { name: "taskManage", label: "定时任务管理" },
  { name: "task", label: "定时任务记录" },
]);
// è¡¨æ ¼æ•°æ®
@@ -122,233 +120,244 @@
// åˆ—配置
const columns = ref([
    { prop: "taskName", label: "巡检任务名称", minWidth: 160 },
    { prop: "remarks", label: "备注", minWidth: 150 },
    { prop: "inspector", label: "执行巡检人", minWidth: 150, slot: "inspector" },
    {
        prop: "frequencyType",
        label: "频次",
        minWidth: 150,
        formatData: (cell) => ({
            DAILY: "每日",
            WEEKLY: "每周",
            MONTHLY: "每月",
            QUARTERLY: "季度"
        }[cell] || "")
    },
    {
        prop: "frequencyDetail",
        label: "开始日期与时间",
        minWidth: 150,
        formatter: (row, column, cellValue) => {
            // å…ˆåˆ¤æ–­æ˜¯å¦æ˜¯å­—符串
            if (typeof cellValue !== 'string') return '';
            let val = cellValue;
            const replacements = {
                MON: '周一',
                TUE: '周二',
                WED: '周三',
                THU: '周四',
                FRI: '周五',
                SAT: '周六',
                SUN: '周日'
            };
            // ä½¿ç”¨æ­£åˆ™ä¸€æ¬¡æ€§æ›¿æ¢æ‰€æœ‰åŒ¹é…é¡¹
            return val.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => replacements[match]);
        }
    },
    { prop: "registrant", label: "登记人", minWidth: 100 },
    { prop: "dateStr", label: "登记日期", minWidth: 100 },
  { prop: "taskName", label: "巡检任务名称", minWidth: 160 },
  { prop: "remarks", label: "备注", minWidth: 150 },
  { prop: "inspector", label: "执行巡检人", minWidth: 150, slot: "inspector" },
  {
    prop: "frequencyType",
    label: "频次",
    minWidth: 150,
    formatter: (_, __, val) => ({
      DAILY: "每日",
      WEEKLY: "每周",
      MONTHLY: "每月",
      QUARTERLY: "季度"
    }[val] || "")
  },
  {
    prop: "frequencyDetail",
    label: "开始日期与时间",
    minWidth: 150,
    formatter: (row, column, cellValue) => {
      // å…ˆåˆ¤æ–­æ˜¯å¦æ˜¯å­—符串
      if (typeof cellValue !== 'string') return '';
      let val = cellValue;
      const replacements = {
        MON: '周一',
        TUE: '周二',
        WED: '周三',
        THU: '周四',
        FRI: '周五',
        SAT: '周六',
        SUN: '周日'
      };
      // ä½¿ç”¨æ­£åˆ™ä¸€æ¬¡æ€§æ›¿æ¢æ‰€æœ‰åŒ¹é…é¡¹
      return val.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => replacements[match]);
    }
  },
  { prop: "registrant", label: "登记人", minWidth: 100 },
  { prop: "createTime", label: "登记日期", minWidth: 100 },
]);
// æ“ä½œåˆ—配置
const getOperationColumn = (operations) => {
    if (!operations || operations.length === 0) return null;
    const operationConfig = {
        label: "操作",
        width: 130,
        fixed: "right",
        dataType: "action",
        operation: operations.map(op => {
            switch (op) {
                case 'edit':
                    return {
                        name: "编辑",
                        clickFun: handleAdd,
                        color: "#409EFF"
                    };
                case 'viewFile':
                    return {
                        name: "查看附件",
                        clickFun: viewFile,
                        color: "#67C23A"
                    };
                default:
                    return null;
            }
        }).filter(Boolean)
    };
    return operationConfig;
  if (!operations || operations.length === 0) return null;
  const operationConfig = {
    label: "操作",
    width: 130,
    fixed: "right",
    dataType: "action",
    operation: operations.map(op => {
      switch (op) {
        case 'edit':
          return {
            name: "编辑",
            clickFun: handleAdd,
            color: "#409EFF"
          };
        case 'viewFile':
          return {
            name: "查看附件",
            clickFun: viewFile,
            color: "#67C23A"
          };
        default:
          return null;
      }
    }).filter(Boolean)
  };
  return operationConfig;
};
onMounted(() => {
    radioChange('taskManage');
  radioChange('taskManage');
});
// å•选变化
const radioChange = (value) => {
    if (value === "taskManage") {
        const operationColumn = getOperationColumn(['edit']);
        tableColumns.value = [...columns.value, ...(operationColumn ? [operationColumn] : [])];
        operationsArr.value = ['edit'];
    } else if (value === "task") {
        const operationColumn = getOperationColumn(['viewFile']);
        tableColumns.value = [...columns.value, ...(operationColumn ? [operationColumn] : [])];
        operationsArr.value = ['viewFile'];
    }
    pageNum.value = 1;
    pageSize.value = 10;
    getList();
  if (value === "taskManage") {
    const operationColumn = getOperationColumn(['edit']);
    tableColumns.value = [...columns.value, ...(operationColumn ? [operationColumn] : [])];
    operationsArr.value = ['edit'];
  } else if (value === "task") {
    const operationColumn = getOperationColumn(['viewFile']);
    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;
  pageSize.value = 10;
  getList();
};
// æŸ¥è¯¢æ“ä½œ
const handleQuery = () => {
    pageNum.value = 1;
    pageSize.value = 10;
    getList();
  pageNum.value = 1;
  pageSize.value = 10;
  getList();
};
const pagination = (obj) => {
    pageNum.value = obj.page;
    pageSize.value = obj.limit;
// åˆ†é¡µå¤„理
const handlePagination = (val) => {
    pageNum.value = val.page;
    pageSize.value = val.limit;
    getList();
};
// èŽ·å–åˆ—è¡¨æ•°æ®
const getList = () => {
    tableLoading.value = true;
    const params = { ...queryParams, size: pageSize.value, current: pageNum.value };
    let apiCall;
    if (activeRadio.value === "task") {
        apiCall = inspectionTaskList(params);
    } else {
        apiCall = timingTaskList(params);
    }
    apiCall.then(res => {
        const rawData = res.data.records || [];
        // å¤„理 inspector å­—段,将字符串转换为数组(适用于所有情况)
        tableData.value = rawData.map(item => {
            const processedItem = { ...item };
            // å¤„理 inspector å­—段
            if (processedItem.inspector) {
                if (typeof processedItem.inspector === 'string') {
                    // å­—符串按逗号分割
                    processedItem.inspector = processedItem.inspector.split(',').map(s => s.trim()).filter(s => s);
                } else if (!Array.isArray(processedItem.inspector)) {
                    // éžæ•°ç»„转为数组
                    processedItem.inspector = [processedItem.inspector];
                }
            } else {
                // ç©ºå€¼è®¾ä¸ºç©ºæ•°ç»„
                processedItem.inspector = [];
            }
            return processedItem;
        });
        total.value = res.data.total || 0;
    }).finally(() => {
        tableLoading.value = false;
    });
  tableLoading.value = true;
  const params = { ...queryParams, size: pageSize.value, current: pageNum.value };
  let apiCall;
  if (activeRadio.value === "task") {
    apiCall = inspectionTaskList(params);
  } else {
    apiCall = timingTaskList(params);
  }
  apiCall.then(res => {
    const rawData = res.data.records || [];
    // å¤„理 inspector å­—段,将字符串转换为数组(适用于所有情况)
    tableData.value = rawData.map(item => {
      const processedItem = { ...item };
      // å¤„理 inspector å­—段
      if (processedItem.inspector) {
        if (typeof processedItem.inspector === 'string') {
          // å­—符串按逗号分割
          processedItem.inspector = processedItem.inspector.split(',').map(s => s.trim()).filter(s => s);
        } else if (!Array.isArray(processedItem.inspector)) {
          // éžæ•°ç»„转为数组
          processedItem.inspector = [processedItem.inspector];
        }
      } else {
        // ç©ºå€¼è®¾ä¸ºç©ºæ•°ç»„
        processedItem.inspector = [];
      }
      return processedItem;
    });
    total.value = res.data.total || 0;
  }).finally(() => {
    tableLoading.value = false;
  });
};
// é‡ç½®æŸ¥è¯¢
const resetQuery = () => {
    for (const key in queryParams) {
        if (!["pageNum", "pageSize"].includes(key)) {
            queryParams[key] = "";
        }
    }
    handleQuery();
  for (const key in queryParams) {
    if (!["pageNum", "pageSize"].includes(key)) {
      queryParams[key] = "";
    }
  }
  handleQuery();
};
// æ–°å¢ž / ç¼–辑
const handleAdd = (row) => {
    const type = row ? 'edit' : 'add';
    nextTick(() => {
        formDia.value?.openDialog(type, row);
    });
  const type = row ? 'edit' : 'add';
  nextTick(() => {
    formDia.value?.openDialog(type, row);
  });
};
// æŸ¥çœ‹é™„ä»¶
const viewFile = (row) => {
    nextTick(() => {
        viewFiles.value?.openDialog(row);
    });
  nextTick(() => {
    viewFiles.value?.openDialog(row);
  });
};
// åˆ é™¤æ“ä½œ
const handleDelete = () => {
    if (!selectedRows.value.length) {
        proxy.$modal.msgWarning("请选择要删除的数据");
        return;
    }
    const deleteIds = selectedRows.value.map(item => item.id);
    proxy.$modal.confirm('是否确认删除所选数据项?').then(() => {
        return delTimingTask(deleteIds);
    }).then(() => {
        proxy.$modal.msgSuccess("删除成功");
        handleQuery();
    }).catch(() => {});
  if (!selectedRows.value.length) {
    proxy.$modal.msgWarning("请选择要删除的数据");
    return;
  }
  const deleteIds = selectedRows.value.map(item => item.id);
  proxy.$modal.confirm('是否确认删除所选数据项?').then(() => {
    return delTimingTask(deleteIds);
  }).then(() => {
    proxy.$modal.msgSuccess("删除成功");
    handleQuery();
  }).catch(() => {});
};
// å¤šé€‰å˜æ›´
const handleSelectionChange = (selection) => {
    selectedRows.value = selection;
  selectedRows.value = selection;
};
// å¯¼å‡º
const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            // æ ¹æ®å½“前选中的标签页调用不同的导出接口
            if (activeRadio.value === "taskManage") {
                // å®šæ—¶ä»»åŠ¡ç®¡ç†
                proxy.download("/timingTask/export", {}, "定时任务管理.xlsx");
            } else if (activeRadio.value === "task") {
                // å®šæ—¶ä»»åŠ¡è®°å½•
                proxy.download("/inspectionTask/export", {}, "定时任务记录.xlsx");
            }
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      // æ ¹æ®å½“前选中的标签页调用不同的导出接口
      if (activeRadio.value === "taskManage") {
        // å®šæ—¶ä»»åŠ¡ç®¡ç†
        proxy.download("/timingTask/export", {}, "定时任务管理.xlsx");
      } else if (activeRadio.value === "task") {
        // å®šæ—¶ä»»åŠ¡è®°å½•
        proxy.download("/inspectionTask/export", {}, "定时任务记录.xlsx");
      }
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
</script>
<style scoped>
.person-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.person-tag {
    margin-right: 4px;
    margin-bottom: 2px;
  margin-right: 4px;
  margin-bottom: 2px;
}
.no-data {
    color: #909399;
    font-size: 14px;
  color: #909399;
  font-size: 14px;
}
</style>
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 = () => {
src/views/equipmentManagement/ledger/Modal.vue
@@ -1,5 +1,5 @@
<template>
  <el-dialog :title="modalOptions.title" v-model="visible" @close="close" draggable>
  <el-dialog :title="modalOptions.title" v-model="visible" @close="close">
    <Form ref="formRef"></Form>
    <template #footer>
            <el-button type="primary" @click="sendForm" :loading="loading">
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,13 +83,58 @@
      </PIMTable>
    </div>
    <Modal ref="modalRef" @success="getTableData"></Modal>
    <el-dialog v-model="qrDialogVisible" title="二维码" width="300px" draggable>
    <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>仅允许导入xls、xlsx格式文件。</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>
@@ -103,6 +149,7 @@
import dayjs from "dayjs";
import QRCode from "qrcode";
import { ref } from "vue";
import { getToken } from "@/utils/auth.js";
defineOptions({
  name: "设备台账",
@@ -171,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: "启用折旧",
@@ -231,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) => {
@@ -275,6 +370,11 @@
  }
  getTableData();
};
/** å¯¼å…¥æŒ‰é’®æ“ä½œ */
const handleImport = () => {
  upload.title = "设备台账导入";
  upload.open = true;
}
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
@@ -299,10 +399,44 @@
};
const downloadQRCode = () => {
  const a = document.createElement("a");
  a.href = qrCodeUrl.value;
  a.download = `${qrRowData.value.deviceName || "二维码"}.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(() => {
src/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue
@@ -4,7 +4,6 @@
            v-model="dialogFormVisible"
            title="计量器具"
            width="50%"
            draggable
            @close="closeDia"
        >
            <el-form
@@ -68,9 +67,7 @@
                            <el-select
                                v-model="form.userId"
                                placeholder="请选择"
                filterable
                default-first-option
                :reserve-keyword="false"
                                disabled
                                clearable
                            >
                                <el-option
@@ -132,7 +129,6 @@
import {getToken} from "@/utils/auth.js";
import {ledgerRecordUpdate, ledgerRecordVerifying} from "@/api/equipmentManagement/calibration.js";
import {delLedgerFile} from "@/api/salesManagement/salesLedger.js";
import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
@@ -252,6 +248,14 @@
    dialogFormVisible.value = false;
    emit('close')
};
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
    const day = String(today.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
}
defineExpose({
    openDialog,
});
src/views/equipmentManagement/measurementEquipment/components/formDia.vue
@@ -4,7 +4,6 @@
        v-model="dialogFormVisible"
        title="计量器具"
        width="50%"
                draggable
        @close="closeDia"
    >
            <el-form
@@ -65,9 +64,7 @@
                                v-model="form.userId"
                                placeholder="请选择"
                                clearable
                filterable
                default-first-option
                :reserve-keyword="false"
                                disabled
                            >
                                <el-option
                                    v-for="item in userList"
@@ -127,7 +124,6 @@
import {afterSalesServiceAdd, afterSalesServiceUpdate} from "@/api/customerService/index.js";
import {getToken} from "@/utils/auth.js";
import {measuringInstrumentAdd, measuringInstrumentUpdate} from "@/api/equipmentManagement/measurementEquipment.js";
import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
@@ -238,6 +234,14 @@
  dialogFormVisible.value = false;
  emit('close')
};
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
    const day = String(today.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
}
defineExpose({
  openDialog,
});
src/views/equipmentManagement/measurementEquipment/index.vue
@@ -148,13 +148,13 @@
                    openCalibrationDia("verifying", row);
                },
            },
            // {
            //     name: "附件",
            //     type: "text",
            //     clickFun: (row) => {
      //     openFilesFormDia(row);
            //     },
            // },
            {
                name: "附件",
                type: "text",
                clickFun: (row) => {
          openFilesFormDia(row);
                },
            },
        ],
    },
]);
@@ -221,6 +221,12 @@
const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
        // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
        const unauthorizedData = selectedRows.value.filter(item => item.userId !== userStore.id);
        if (unauthorizedData.length > 0) {
            proxy.$modal.msgWarning("不可删除他人维护的数据");
            return;
        }
        ids = selectedRows.value.map((item) => item.id);
    } else {
        proxy.$modal.msgWarning("请选择数据");
src/views/equipmentManagement/repair/Form/MaintainForm.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/repair/Form/RepairForm.vue
ÎļþÒÑɾ³ý
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="请输入监督人" 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>
src/views/equipmentManagement/repair/Modal/MaintainModal.vue
@@ -1,53 +1,116 @@
<template>
  <el-dialog v-model="visible" :title="modalOptions.title" direction="ltr" draggable>
    <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: "设备维修" });
// ä¿å­˜æŠ¥ä¿®è®°å½•çš„id
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; // ä¿å­˜æŠ¥ä¿®è®°å½•çš„id
  visible.value = true;
  await nextTick();
  maintainFormRef.value.setForm(row);
  setForm(row);
};
defineExpose({
src/views/equipmentManagement/repair/Modal/RepairModal.vue
@@ -1,24 +1,113 @@
<template>
  <el-dialog v-model="visible" :title="modalOptions.title" @close="close" draggable>
    <RepairForm ref="repairFormRef" :id="id" />
    <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,63 +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 () => {
  try {
    // å¼€å§‹åŠ è½½
  await formRef.value?.validate(async (valid) => {
    if (!valid) return;
    loading.value = true;
    // æäº¤è¡¨å•并获取校验结果
    const submitStatus = await repairFormRef.value.submitForm();
    if (!submitStatus) {
      // å¦‚果表单验证失败,取消加载状态
      loading.value = false;
      return;
    }
    // èŽ·å–è¡¨å•æ•°æ®
    const form = await repairFormRef.value.getForm();
    // æ ¹æ®æ˜¯å¦æœ‰ID决定是编辑还是新增
    const { code } = id.value
    try {
      const { code } = id.value
        ? await editRepair({ id: unref(id), ...form })
        : await addRepair(form);
    if (code === 200) {
      ElMessage.success(`${id ? "编辑" : "新增"}报修成功`);
      emits("ok");
      if (code == 200) {
        ElMessage.success(`${id.value ? "编辑" : "新增"}报修成功`);
        visible.value = false;
        emits("ok");
      }
    } finally {
      loading.value = false;
    }
  } catch (error) {
  } finally {
    // æ— è®ºæˆåŠŸè¿˜æ˜¯å¤±è´¥ï¼Œéƒ½å–æ¶ˆåŠ è½½çŠ¶æ€
    loading.value = false;
    closeModal();
  }
  });
};
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({
@@ -90,3 +217,5 @@
  openEdit,
});
</script>
<style lang="scss" scoped></style>
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,15 +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 || multipleList[0]?.status !== 1"
            @click="addMaintain"
          >
            æ–°å¢žç»´ä¿®
          </el-button>
          <el-button type="success" icon="Van" @click="addRepair">
            æ–°å¢žæŠ¥ä¿®
          </el-button>
@@ -86,79 +73,94 @@
          <el-button
            type="danger"
            icon="Delete"
            :disabled="multipleList.length <= 0"
            @click="delRepairByIds(multipleList)"
            :disabled="multipleList.length <= 0 || hasFinishedStatus"
            @click="delRepairByIds(multipleList.map((item) => item.id))"
          >
            æ‰¹é‡åˆ é™¤
          </el-button>
        </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 === 5" type="danger">维修失败</el-tag>
          <el-tag v-if="row.status === 4" type="danger">维修成功</el-tag>
          <el-tag v-if="row.status === 3" type="danger">维修中</el-tag>
          <el-tag v-if="row.status === 2" type="danger">审核失败</el-tag>
          <el-tag v-if="row.status === 1" type="success">审核通过</el-tag>
          <el-tag v-if="row.status === 0" type="warning">审核中</el-tag>
          <el-tag v-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)"
            :disabled="row.status !== 0"
          >
            ç¼–辑
          </el-button>
          <el-button
            type="danger"
            text
            icon="delete"
            @click="delRepairByIds(row)"
            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"
            link
            :disabled="row.status === 1"
            @click="delRepairByIds(row.id)"
          >
            åˆ é™¤
          </el-button>
        </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([]);
@@ -173,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");
    }
@@ -264,6 +268,11 @@
  multipleList.value = selectionList;
};
// æ£€æŸ¥é€‰ä¸­çš„记录中是否有完结状态的
const hasFinishedStatus = computed(() => {
  return multipleList.value.some(item => item.status === 1)
})
// æ–°å¢žæŠ¥ä¿®
const addRepair = () => {
  repairModalRef.value.openAdd();
@@ -275,36 +284,33 @@
};
// æ–°å¢žç»´ä¿®
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) => {
  let isDel = false
  if(Array.isArray(ids)){
    ids.forEach((item)=>{
      if(item.status !== 0){
        isDel = true
      }
    })
  }else{
    if(ids.status !== 0){
      isDel = true
    }
  }
  // æ£€æŸ¥æ˜¯å¦æœ‰å®Œç»“状态的记录
  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(isDel){
    ElMessage.warning("只能删除审核中的报修数据");
    return
  if (hasFinished) {
    ElMessage.warning('不能删除状态为完结的记录');
    return;
  }
  ElMessageBox.confirm("确认删除报修数据, æ­¤æ“ä½œä¸å¯é€†?", "警告", {
@@ -312,17 +318,10 @@
    cancelButtonText: "取消",
    type: "warning",
  }).then(async () => {
    let idsList = ""
    if(Array.isArray(ids)){
      idsList = multipleList.value.map((item) => item.id);
      console.log(idsList)
    }else{
      idsList = ids.id
    }
    const { code } = await delRepair(idsList);
    const {code} = await delRepair(ids);
    if (code === 200) {
      ElMessage.success("删除成功");
      await getTableData();
      getTableData();
    }
  });
};
@@ -334,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(() => {
@@ -351,6 +350,7 @@
.table_list {
  margin-top: unset;
}
.actions {
  display: flex;
  justify-content: space-between;
src/views/equipmentManagement/spareParts/index.vue
@@ -19,47 +19,21 @@
                <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>
    
    <div class="table_list">
      <el-table
        v-loading="loading"
        :data="renderTableData"
        style="width: 100%; margin-top: 10px;"
        border
        row-key="id"
      >
      <el-table-column prop="deviceNameStr" label="设备名称"  width="300"></el-table-column>
        <el-table-column prop="name" label="备件名称" width="200"></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="price" label="ä»·æ ¼" width="140"></el-table-column>
        <el-table-column prop="description" label="描述" width="150"></el-table-column>
        <el-table-column label="操作" width="150" fixed="right" align="center">
          <template #default="{ row }">
            <el-button
              link
                            type="primary"
              @click="() => editCategory(row)"
              :disabled="loading"
            >
              ç¼–辑
            </el-button>
            <el-button
                            link
              @click="() => deleteCategory(row.id)"
              style="color: #f56c6c;"
              :disabled="loading"
            >
              åˆ é™¤
            </el-button>
          </template>
        </el-table-column>
      </el-table>
    </div>
    <el-dialog title="分类管理" v-model="dialogVisible" width="60%">
      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
        <el-form-item label="设备" prop="deviceLedgerIds">
@@ -85,6 +59,9 @@
        </el-form-item>
        <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="请选择状态">
@@ -143,6 +120,66 @@
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:'',
@@ -161,6 +198,9 @@
  ],
  sparePartsNo: [
    { required: true, message: '请输入备件编号', trigger: 'blur' }
  ],
  quantity:[
    { required: true, message: '请输入数量', trigger: 'blur' }
  ],
  status: [
    { required: true, message: '请选择状态', trigger: 'change' }
@@ -208,7 +248,10 @@
const fetchListData = async () => {
  loading.value = true;
  try {
    const params = {};
    const params = {
      current: pagination.current,
      size: pagination.size
    };
    if (queryParams.name) {
      params.name = queryParams.name;
    }
@@ -216,6 +259,7 @@
    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;
@@ -226,12 +270,27 @@
// æŸ¥è¯¢
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();
}
@@ -254,6 +313,7 @@
  form.status = '';
  form.description = '';
  form.deviceLedgerIds = [];
  form.quantity = undefined;
  form.price = null;
  operationType.value = 'add'
  dialogVisible.value = true;
@@ -366,6 +426,13 @@
  margin-top: unset;
}
.pagination-container {
  margin-top: 20px;
  display: flex;
  justify-content: flex-end;
  padding: 16px 0;
}
.el-table__header-wrapper th {
  background-color: #f5f7fa;
  font-weight: 600;
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="请输入监督人" 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: "季度",
  };
  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>
src/views/equipmentManagement/upkeep/Form/MaintenanceForm.vue
ÎļþÒÑɾ³ý
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"]);
// ä¿å­˜è®¡åˆ’保养记录的id
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; // ä¿å­˜è®¡åˆ’保养记录的id
  visible.value = true;
  await nextTick();
  setForm(row);
};
defineExpose({
  open,
});
</script>
<style lang="scss" scoped></style>
src/views/equipmentManagement/upkeep/Form/PlanForm.vue
ÎļþÒÑɾ³ý
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>
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="季度" 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
        }
        // å¦‚果有设备ID数组,转换为数组并设置设备信息
        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()
    }
    // é‡ç½®è¡¨å•数据确保设备信息正确重置
    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 }
                // ä¸å†å‘后端传保养人字段,仅使用接口要求的 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>
src/views/equipmentManagement/upkeep/Modal/MaintenanceModal.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/upkeep/Modal/PlanModal.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/upkeep/Modal/formDia.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/upkeep/index.vue
@@ -17,8 +17,9 @@
            </el-form-item>
            <el-form-item label="任务状态">
              <el-select v-model="scheduledFilters.status" placeholder="请选择任务状态" clearable style="width: 200px">
                <el-option label="启用" value="1" />
                <el-option label="停用" value="0" />
                <el-option label="待审核" value="待审核" />
                <el-option label="审核通过" value="审核通过" />
                <el-option label="审核不通过" value="审核不通过" />
              </el-select>
            </el-form-item>
            <el-form-item>
@@ -58,22 +59,30 @@
            @pagination="changeScheduledPage"
          >
            <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 === '待审核'" 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"
                text
                icon="editPen"
                link
                @click="editScheduledTask(row)"
              >
                ç¼–辑
              </el-button>
              <el-button
                type="warning"
                link
                :disabled="row.status === '审核通过'"
                @click="openScheduledApprove(row)"
              >
                å®¡æ‰¹
              </el-button>
              <el-button
                type="danger"
                text
                icon="delete"
                link
                @click="delScheduledTaskByIds(row.id)"
              >
                åˆ é™¤
@@ -135,24 +144,13 @@
          <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"
                :disabled="multipleList.length <= 0 || hasFinishedStatus"
                @click="delRepairByIds(multipleList.map((item) => item.id))"
              >
                æ‰¹é‡åˆ é™¤
@@ -181,21 +179,44 @@
          <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>
@@ -203,24 +224,41 @@
      </el-tab-pane>
    </el-tabs>
    <PlanModal ref="planModalRef" @ok="getTableData" />
        <MaintenanceModal ref="maintainModalRef" @ok="getTableData" />
        <FormDia ref="formDiaRef" @closeDia="getScheduledTableData" />
    <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 { ref, onMounted, reactive, getCurrentInstance, nextTick } from 'vue'
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 './Modal/PlanModal.vue'
import MaintenanceModal from './Modal/MaintenanceModal.vue'
import FormDia from './Modal/formDia.vue'
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'
const { proxy } = getCurrentInstance()
@@ -234,6 +272,12 @@
const maintainModalRef = ref()
// å®šæ—¶ä»»åŠ¡å¼¹çª—æŽ§åˆ¶å™¨
const formDiaRef = ref()
// ä¿å…»å®¡æ‰¹å¼¹çª—
const approveModalRef = ref()
// é™„件弹窗
const fileListDialogRef = ref(null)
const fileDialogVisible = ref(false)
const currentMaintenanceTaskId = ref(null)
// ä»»åŠ¡è®°å½•tab(原设备保养页面)相关变量
const filters = reactive({
@@ -307,6 +351,15 @@
    },
    { prop: "registrant", label: "登记人", minWidth: 100 },
    { prop: "registrationDate", label: "登记日期", minWidth: 100 },
    { prop: "auditName", label: "审核人", width: 120 },
    { prop: "supervisoryName", label: "监督人", width: 120 },
  {
    label: "状态",
    align: "center",
    prop: "status",
    dataType: "slot",
    slot: "statusRef",
  },
    {
        fixed: "right",
        label: "操作",
@@ -379,7 +432,7 @@
        dataType: "slot",
        slot: "operation",
        align: "center",
        width: "200px",
        width: "350px",
    },
])
@@ -494,15 +547,24 @@
  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 = () => {
  const row = multipleList.value[0]
const addMaintain = (row) => {
  maintainModalRef.value.open(row.id, row)
}
// å®šæ—¶ä»»åŠ¡å®¡æ‰¹
const openScheduledApprove = (row) => {
  approveModalRef.value.open(row)
}
const addPlan = () => {
@@ -514,6 +576,13 @@
}
const delRepairByIds = async (ids) => {
  // æ£€æŸ¥æ˜¯å¦æœ‰å®Œç»“状态的记录
  const hasFinished = multipleList.value.some(item => item.status === 1)
  if (hasFinished) {
    ElMessage.warning('不能删除状态为完结的记录')
    return
  }
  try {
    await ElMessageBox.confirm('确认删除保养数据, æ­¤æ“ä½œä¸å¯é€†?', '警告', {
      confirmButtonText: '确定',
@@ -554,6 +623,79 @@
  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(() => {
  // æ ¹æ®é»˜è®¤æ¿€æ´»çš„ Tab è°ƒç”¨å¯¹åº”的查询接口
  if (activeTab.value === 'scheduled') {