gongchunyi
10 天以前 b88332e0a2686e68023e26438af97f79247b1ddd
feat: 设备保养修改,新增保养人、详情
已添加1个文件
已修改6个文件
677 ■■■■ 文件已修改
src/api/equipmentManagement/maintenanceTaskFile.js 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Dialog/FileListDialog.vue 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/PlanModal.vue 245 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/UpkeepDetailModal.vue 202 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/formDia.vue 80 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/index.vue 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/maintenanceTaskFile.js
@@ -18,6 +18,20 @@
  });
}
export function bindMaintenanceTaskFile(data) {
  return addMaintenanceTaskFile(data);
}
// ä¸Šä¼ ä¿å…»è®¡åˆ’附件
export function uploadMaintenanceTaskFile(formData) {
  return request({
    url: "/maintenanceTaskFile/upload",
    method: "post",
    data: formData,
    headers: { "Content-Type": "multipart/form-data" },
  });
}
// åˆ é™¤ä¿å…»ä»»åС附件
export function delMaintenanceTaskFile(id) {
  return request({
src/components/Dialog/FileListDialog.vue
@@ -10,6 +10,7 @@
                   class="upload-demo"
                   :action="uploadAction"
                   :headers="uploadHeaders"
                   :data="uploadData"
                   :show-file-list="false"
                   :on-success="handleDefaultUploadSuccess"
                   :on-error="handleDefaultUploadError">
@@ -169,6 +170,14 @@
      type: String,
      default: `${import.meta.env.VITE_APP_BASE_API}/file/upload`,
    },
    uploadData: {
      type: Object,
      default: () => ({}),
    },
    uploadDirectSave: {
      type: Boolean,
      default: false,
    },
    isShowPagination: {
      type: Boolean,
      default: false,
@@ -278,8 +287,13 @@
      ElMessage.error(res?.msg || "文件上传失败");
      return;
    }
    if (props.uploadDirectSave) {
      ElMessage.success("上传成功");
      emit("upload", res?.data || {});
      return;
    }
    if (!props.rulesRegulationsManagementId) {
      ElMessage.error("缺少规章制度ID,无法保存附件");
      ElMessage.error("缺少业务ID,无法保存附件");
      return;
    }
    const fileName = res?.data?.originalName || file?.name;
src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue
@@ -2,7 +2,7 @@
  <FormDialog
    v-model="visible"
    :title="'设备保养'"
    width="500px"
    width="640px"
    @confirm="sendForm"
    @cancel="handleCancel"
    @close="handleClose"
@@ -35,8 +35,13 @@
      <el-form-item label="保养结果">
        <el-input
          v-model="form.maintenanceResult"
          type="textarea"
          :rows="6"
          placeholder="请输入保养结果"
          type="text" />
          maxlength="2000"
          show-word-limit
          resize="vertical"
        />
      </el-form-item>
    </el-form>
  </FormDialog>
src/views/equipmentManagement/upkeep/Form/PlanModal.vue
@@ -2,7 +2,7 @@
  <FormDialog
    v-model="visible"
    :title="id ? '编辑设备保养计划' : '新增设备保养计划'"
    width="500px"
    width="640px"
    @confirm="sendForm"
    @cancel="handleCancel"
    @close="handleClose"
@@ -35,25 +35,17 @@
      <el-form-item label="保养项目">
        <el-input
          v-model="form.maintenanceLocation"
          type="textarea"
          :rows="3"
          placeholder="请输入保养项目"
        />
      </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-input
          :model-value="registrantDisplayName"
          disabled
          placeholder="当前登录用户"
        />
      </el-form-item>
      <el-form-item v-if="id" label="保修状态">
        <el-select v-model="form.status">
@@ -73,6 +65,18 @@
          clearable
        />
      </el-form-item>
      <el-form-item label="附件">
        <el-upload
          :http-request="handlePlanFileUpload"
          :file-list="planFileList"
          :on-remove="handlePlanFileRemove"
          multiple
          list-type="picture-card"
        >
          <el-icon><Plus /></el-icon>
        </el-upload>
        <span v-if="!id" class="upload-tip">可先选择附件,保存计划后自动关联到本计划</span>
      </el-form-item>
    </el-form>
  </FormDialog>
</template>
@@ -84,18 +88,42 @@
  editUpkeep,
  getUpkeepById,
} from "@/api/equipmentManagement/upkeep";
import {
  listMaintenanceTaskFiles,
  bindMaintenanceTaskFile,
  uploadMaintenanceTaskFile,
  delMaintenanceTaskFile,
} from "@/api/equipmentManagement/maintenanceTaskFile";
import { ElMessage } from "element-plus";
import { Plus } from "@element-plus/icons-vue";
import useFormData from "@/hooks/useFormData";
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
import { onMounted } from "vue";
import { computed, onMounted, ref } from "vue";
import dayjs from "dayjs";
import { userListNoPage } from "@/api/system/user.js";
import useUserStore from "@/store/modules/user.js";
import request from "@/utils/request";
defineOptions({
  name: "设备保养新增计划",
});
const emits = defineEmits(["ok"]);
const userStore = useUserStore();
const javaApi = import.meta.env.VITE_APP_BASE_API;
const pendingTempFiles = ref([]);
const planFileList = ref([]);
const registrantDisplayName = computed(
  () => userStore.nickName || userStore.name || "当前登录用户"
);
const syncCreateUserFromLogin = () => {
  if (userStore.id != null && userStore.id !== "") {
    form.createUser = userStore.id;
  }
};
const id = ref();
const visible = ref(false);
@@ -108,64 +136,178 @@
};
const { form, resetForm } = useFormData({
  deviceLedgerId: undefined, // è®¾å¤‡Id
  deviceName: undefined, // è®¾å¤‡åç§°
  deviceModel: undefined, // è§„格型号
  maintenanceLocation: undefined, // ä¿å…»é¡¹ç›®
  maintenancePlanTime: undefined, // è®¡åˆ’保养日期
  createUser: undefined, // å½•入人
  status: 0, //保修状态
  deviceLedgerId: undefined,
  deviceName: undefined,
  deviceModel: undefined,
  maintenanceLocation: undefined,
  maintenancePlanTime: undefined,
  createUser: undefined,
  status: 0,
});
const setDeviceModel = (deviceId) => {
  const option = deviceOptions.value.find((item) => item.id === deviceId);
  form.deviceModel = option.deviceModel;
  if (option) {
    form.deviceModel = option.deviceModel;
  }
};
/**
 * @desc è®¾ç½®è¡¨å•内容
 * @param data è®¾å¤‡ä¿¡æ¯
 */
const resetAttachmentState = () => {
  pendingTempFiles.value = [];
  planFileList.value = [];
};
const normalizeFilePreviewUrl = (url = "") => {
  if (!url) return "";
  if (url.startsWith("http")) return url;
  if (url.startsWith("/profile")) return javaApi + url;
  return url;
};
const loadPlanFiles = async (planId) => {
  if (!planId) return;
  const res = await listMaintenanceTaskFiles({
    current: 1,
    size: 100,
    deviceMaintenanceId: planId,
  });
  const records = res?.data?.records || [];
  planFileList.value = records.map((item) => ({
    name: item.name,
    url: normalizeFilePreviewUrl(item.url),
    status: "success",
    uid: `saved-${item.id}`,
    fileId: item.id,
  }));
};
const uploadTempFile = (file) => {
  const fd = new FormData();
  fd.append("file", file);
  fd.append("type", "16");
  return request({
    url: "/file/upload",
    method: "post",
    data: fd,
    headers: { "Content-Type": "multipart/form-data" },
  });
};
const handlePlanFileUpload = async (options) => {
  const { file, onSuccess, onError } = options;
  try {
    if (id.value) {
      const fd = new FormData();
      fd.append("file", file);
      fd.append("deviceMaintenanceId", String(id.value));
      const res = await uploadMaintenanceTaskFile(fd);
      if (res.code === 200) {
        await loadPlanFiles(id.value);
        onSuccess(res);
        ElMessage.success("附件上传成功");
      } else {
        onError(new Error(res.msg || "上传失败"));
      }
      return;
    }
    const res = await uploadTempFile(file);
    if (res.code !== 200) {
      onError(new Error(res.msg || "上传失败"));
      return;
    }
    const data = res.data || {};
    pendingTempFiles.value.push({
      tempId: data.tempId,
      name: data.originalName || file.name,
    });
    onSuccess(res);
    planFileList.value.push({
      name: data.originalName || file.name,
      url: "",
      status: "success",
      uid: data.tempId,
      tempId: data.tempId,
    });
  } catch (e) {
    onError(e);
    ElMessage.error("附件上传失败");
  }
};
const handlePlanFileRemove = async (file) => {
  if (file.fileId) {
    try {
      await delMaintenanceTaskFile(file.fileId);
      await loadPlanFiles(id.value);
    } catch (e) {
      ElMessage.error("删除附件失败");
    }
    return;
  }
  const tempId = file.tempId || file.uid;
  pendingTempFiles.value = pendingTempFiles.value.filter((f) => f.tempId !== tempId);
  planFileList.value = planFileList.value.filter((f) => (f.tempId || f.uid) !== tempId);
};
const bindPendingFiles = async (planId) => {
  if (!pendingTempFiles.value.length) return;
  for (const item of pendingTempFiles.value) {
    await bindMaintenanceTaskFile({
      tempId: item.tempId,
      name: item.name,
      deviceMaintenanceId: planId,
    });
  }
};
const setForm = (data) => {
  form.deviceLedgerId = data.deviceLedgerId;
  form.deviceName = data.deviceName;
  form.deviceModel = data.deviceModel;
  form.maintenanceLocation = data.maintenanceLocation;
  form.createUser = Number(data.createUser);
  form.status = data.status;
  syncCreateUserFromLogin();
  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) => {
  resetAttachmentState();
  const { data } = await getUpkeepById(editId);
  id.value = editId;
  visible.value = true;
  await nextTick();
  setForm(data);
  await loadPlanFiles(editId);
};
const sendForm = async () => {
  syncCreateUserFromLogin();
  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");
    if (id.value) {
      const { code } = await editUpkeep({ id: unref(id), ...form });
      if (code == 200) {
        ElMessage.success("编辑计划成功");
        visible.value = false;
        emits("ok");
      }
    } else {
      const res = await addUpkeep(form);
      if (res.code == 200) {
        const planId = res.data?.id;
        if (planId) {
          await bindPendingFiles(planId);
        }
        ElMessage.success("新增计划成功");
        visible.value = false;
        emits("ok");
      }
    }
  } finally {
    loading.value = false;
@@ -174,16 +316,21 @@
const handleCancel = () => {
  resetForm();
  resetAttachmentState();
  visible.value = false;
};
const handleClose = () => {
  resetForm();
  resetAttachmentState();
  visible.value = false;
};
const openModal = () => {
  id.value = undefined;
  resetForm();
  resetAttachmentState();
  syncCreateUserFromLogin();
  visible.value = true;
};
@@ -193,4 +340,12 @@
});
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.upload-tip {
  display: block;
  font-size: 12px;
  color: #999;
  margin-top: 8px;
  line-height: 1.4;
}
</style>
src/views/equipmentManagement/upkeep/Form/UpkeepDetailModal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,202 @@
<template>
  <el-dialog
    v-model="visible"
    title="保养计划详情"
    width="820px"
    destroy-on-close
    @closed="onClosed"
  >
    <div v-loading="loading" class="upkeep-detail">
      <template v-if="detail">
        <el-descriptions :column="2" border size="small">
          <el-descriptions-item label="设备名称">{{ detail.deviceName || "—" }}</el-descriptions-item>
          <el-descriptions-item label="规格型号">{{ detail.deviceModel || "—" }}</el-descriptions-item>
          <el-descriptions-item label="保养项目" :span="2">
            <div class="multiline">{{ detail.maintenanceLocation || "—" }}</div>
          </el-descriptions-item>
          <el-descriptions-item label="保养人">{{ detail.maintenancePerson || "—" }}</el-descriptions-item>
          <el-descriptions-item label="计划保养日期">{{ fmtDate(detail.maintenancePlanTime) }}</el-descriptions-item>
          <el-descriptions-item label="录入人">{{ detail.createUserName || "—" }}</el-descriptions-item>
          <el-descriptions-item label="实际保养人">{{ detail.maintenanceActuallyName || "—" }}</el-descriptions-item>
          <el-descriptions-item label="实际保养日期">{{ fmtDateTime(detail.maintenanceActuallyTime) }}</el-descriptions-item>
          <el-descriptions-item label="状态">
            <el-tag v-if="detail.status === 2" type="danger" size="small">失败</el-tag>
            <el-tag v-else-if="detail.status === 1" type="success" size="small">完结</el-tag>
            <el-tag v-else-if="detail.status === 0" type="warning" size="small">待保养</el-tag>
            <span v-else>—</span>
          </el-descriptions-item>
          <el-descriptions-item label="保养结果" :span="2">
            <div class="multiline">{{ detail.maintenanceResult || "—" }}</div>
          </el-descriptions-item>
          <el-descriptions-item label="录入时间">{{ fmtDateTime(detail.createTime) }}</el-descriptions-item>
          <el-descriptions-item label="更新时间">{{ fmtDateTime(detail.updateTime) }}</el-descriptions-item>
        </el-descriptions>
        <div class="attach-block">
          <div class="attach-title">附件({{ fileList.length }})</div>
          <div v-if="fileList.length" class="img-grid">
            <div v-for="file in fileList" :key="file.id" class="attach-item">
              <el-image
                v-if="isImageFile(file.name)"
                :src="file.url"
                fit="cover"
                class="thumb"
                :preview-src-list="imagePreviewList"
                preview-teleported
              />
              <div v-else class="file-chip">
                <el-link type="primary" :href="file.url" target="_blank">{{ file.name }}</el-link>
              </div>
              <div class="file-name" :title="file.name">{{ file.name }}</div>
            </div>
          </div>
          <el-empty v-else description="暂无附件" :image-size="56" />
        </div>
      </template>
    </div>
    <template #footer>
      <el-button type="primary" @click="visible = false">关闭</el-button>
    </template>
  </el-dialog>
</template>
<script setup>
import { computed, ref } from "vue";
import dayjs from "dayjs";
import { getUpkeepById } from "@/api/equipmentManagement/upkeep";
import { listMaintenanceTaskFiles } from "@/api/equipmentManagement/maintenanceTaskFile";
defineOptions({ name: "UpkeepDetailModal" });
const props = defineProps({
  javaApi: {
    type: String,
    default: "",
  },
});
const visible = ref(false);
const loading = ref(false);
const detail = ref(null);
const fileList = ref([]);
const apiBase = computed(() => props.javaApi || import.meta.env.VITE_APP_BASE_API || "");
const imagePreviewList = computed(() =>
  fileList.value.filter((f) => isImageFile(f.name)).map((f) => f.url)
);
const fmtDate = (v) => (v ? dayjs(v).format("YYYY-MM-DD") : "—");
const fmtDateTime = (v) => (v ? dayjs(v).format("YYYY-MM-DD HH:mm:ss") : "—");
const isImageFile = (name = "") => /\.(jpg|jpeg|png|gif|webp|bmp)$/i.test(name);
const normalizeFileUrl = (rawUrl = "") => {
  let fileUrl = rawUrl || "";
  if (!fileUrl) return "";
  if (fileUrl.startsWith("http://") || fileUrl.startsWith("https://")) return fileUrl;
  if (fileUrl.indexOf("\\") > -1) {
    const lowerPath = fileUrl.toLowerCase();
    const uploadPathIndex = lowerPath.indexOf("uploadpath");
    if (uploadPathIndex > -1) {
      fileUrl = fileUrl.substring(uploadPathIndex).replace(/\\/g, "/");
    } else {
      fileUrl = fileUrl.replace(/\\/g, "/");
    }
  }
  fileUrl = fileUrl.replace(/^\/?uploadPath/i, "/profile");
  if (!fileUrl.startsWith("/")) fileUrl = "/" + fileUrl;
  if (!fileUrl.startsWith(apiBase.value)) fileUrl = apiBase.value + fileUrl;
  return fileUrl;
};
const loadFiles = async (id) => {
  const res = await listMaintenanceTaskFiles({
    current: 1,
    size: 100,
    deviceMaintenanceId: id,
  });
  const records = res?.data?.records || [];
  fileList.value = records.map((item) => ({
    id: item.id,
    name: item.name,
    url: normalizeFileUrl(item.url),
  }));
};
const open = async (row) => {
  if (!row?.id) return;
  visible.value = true;
  loading.value = true;
  detail.value = null;
  fileList.value = [];
  try {
    const { data } = await getUpkeepById(row.id);
    detail.value = { ...row, ...data };
    await loadFiles(row.id);
  } finally {
    loading.value = false;
  }
};
const onClosed = () => {
  detail.value = null;
  fileList.value = [];
};
defineExpose({ open });
</script>
<style lang="scss" scoped>
.upkeep-detail {
  min-height: 120px;
}
.multiline {
  white-space: pre-wrap;
  word-break: break-word;
  line-height: 1.6;
}
.attach-block {
  margin-top: 20px;
}
.attach-title {
  font-size: 14px;
  font-weight: 600;
  margin-bottom: 12px;
  color: #303133;
}
.img-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}
.attach-item {
  width: 100px;
}
.thumb {
  width: 100px;
  height: 100px;
  border-radius: 4px;
  border: 1px solid #ebeef5;
}
.file-chip {
  width: 100px;
  height: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 8px;
  border: 1px dashed #dcdfe6;
  border-radius: 4px;
  box-sizing: border-box;
}
.file-name {
  display: block;
  margin-top: 6px;
  font-size: 12px;
  color: #606266;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
</style>
src/views/equipmentManagement/upkeep/Form/formDia.vue
@@ -35,8 +35,17 @@
            <el-row>
                <el-col :span="12">
                    <el-form-item label="录入人" prop="inspector">
                        <el-input
                            :model-value="registrantDisplayName"
                            disabled
                            placeholder="当前登录用户"
                        />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="保养人" prop="maintenancePerson">
                        <el-select
                            v-model="form.inspector"
                            v-model="form.maintenancePerson"
                            filterable
                            default-first-option
                            :reserve-keyword="false"
@@ -47,11 +56,13 @@
                                v-for="item in userList"
                                :label="item.nickName"
                                :value="item.userId"
                                :key="item.userId"
                                :key="'mp-' + item.userId"
                            />
                        </el-select>
                    </el-form-item>
                </el-col>
            </el-row>
            <el-row>
                <el-col :span="12">
                    <el-form-item label="登记时间" prop="registrationDate">
                        <el-date-picker
@@ -123,7 +134,19 @@
                </el-col>
            </el-row>
            <el-row>
                <el-col :span="12">
                <el-col :span="24">
                    <el-form-item label="保养项目" prop="maintenanceItems">
                        <el-input
                            v-model="form.maintenanceItems"
                            type="textarea"
                            :rows="3"
                            placeholder="请输入保养项目"
                        />
                    </el-form-item>
                </el-col>
            </el-row>
            <el-row>
                <el-col :span="24">
                    <el-form-item label="备注" prop="remarks">
                        <el-input v-model="form.remarks" placeholder="请输入备注" type="textarea" />
                    </el-form-item>
@@ -135,7 +158,7 @@
<script setup>
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { reactive, ref, getCurrentInstance, toRefs } from "vue";
import { reactive, ref, computed, getCurrentInstance, toRefs } from "vue";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
import { deviceMaintenanceTaskAdd, deviceMaintenanceTaskEdit } from "@/api/equipmentManagement/upkeep";
@@ -152,8 +175,10 @@
    form: {
        taskId: undefined,
        taskName: undefined,
        // å½•入人:单选一个用户 id
        // å½•入人、保养人:用户 id
        inspector: undefined,
        maintenancePerson: undefined,
        maintenanceItems: '',
        remarks: '',
        frequencyType: '',
        frequencyDetail: '',
@@ -165,6 +190,8 @@
    rules: {
        taskId: [{ required: true, message: "请选择设备", trigger: "change" },],
        inspector: [{ required: true, message: "请选择录入人", trigger: "blur" },],
        maintenancePerson: [{ required: true, message: "请选择保养人", trigger: "change" }],
        maintenanceItems: [{ required: true, message: "请输入保养项目", trigger: "blur" }],
        registrationDate: [{ required: true, message: "请选择登记时间", trigger: "change" }],
        frequencyDetail: [{
            validator: (rule, value, callback) => {
@@ -192,6 +219,16 @@
})
const { form, rules } = toRefs(data)
const userList = ref([])
const registrantDisplayName = computed(
    () => userStore.nickName || userStore.name || "当前登录用户"
)
const syncRegistrantFromLogin = () => {
    if (userStore.id != null && userStore.id !== "") {
        form.value.inspector = userStore.id
    }
}
const loadDeviceName = async () => {
    const { data } = await getDeviceLedger();
@@ -226,21 +263,17 @@
    
    if (type === 'edit' && row) {
        form.value = { ...row }
        // ç¼–辑时用接口返回的 registrantId å›žæ˜¾å½•入人
        if (row.registrantId) {
            form.value.inspector = row.registrantId
        if (row.maintenancePersonId) {
            form.value.maintenancePerson = row.maintenancePersonId
        }
        // å¦‚果有设备ID,自动设置设备信息
        if (form.value.taskId) {
            setDeviceModel(form.value.taskId);
        }
    } else if (type === 'add') {
        // æ–°å¢žæ—¶è®¾ç½®ç™»è®°æ—¥æœŸä¸ºå½“天
        form.value.registrationDate = getCurrentDate();
        // æ–°å¢žæ—¶è®¾ç½®å½•入人为当前登录账户
        form.value.inspector = userStore.id;
        form.value.maintenancePerson = userStore.id;
    }
    syncRegistrantFromLogin()
}
// å…³é—­å¯¹è¯æ¡†
@@ -260,7 +293,8 @@
        taskId: undefined,
        taskName: undefined,
        inspector: undefined,
        inspector: undefined,
        maintenancePerson: undefined,
        maintenanceItems: '',
        remarks: '',
        frequencyType: '',
        frequencyDetail: '',
@@ -273,19 +307,21 @@
// æäº¤è¡¨å•
const submitForm = () => {
    syncRegistrantFromLogin()
    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)
                payload.registrantId = userStore.id
                payload.registrant = userStore.nickName || userStore.name
                const maintenancePersonUserId = form.value.maintenancePerson
                if (maintenancePersonUserId) {
                    const maintainer = userList.value.find(
                        (u) => String(u.userId) === String(maintenancePersonUserId)
                    )
                    if (selectedUser) {
                        payload.registrantId = selectedUser.userId
                        payload.registrant = selectedUser.nickName
                    if (maintainer) {
                        payload.maintenancePersonId = maintainer.userId
                        payload.maintenancePerson = maintainer.nickName
                    }
                }
                delete payload.inspector
src/views/equipmentManagement/upkeep/index.vue
@@ -162,23 +162,19 @@
        @selection-change="handleSelectionChange"
        @pagination="changePage"
      >
        <template #maintenanceResultRef="{ row }">
          <div>{{ row.maintenanceResult || '-' }}</div>
        </template>
        <template #statusRef="{ row }">
          <el-tag v-if="row.status === 2" type="danger">失败</el-tag>
          <el-tag v-if="row.status === 1" type="success">完结</el-tag>
          <el-tag v-if="row.status === 0" type="warning">待保养</el-tag>
        </template>
        <template #operation="{ row }">
          <!-- è¿™ä¸ªåŠŸèƒ½è·Ÿæ–°å¢žä¿å…»åŠŸèƒ½ä¸€æ¨¡ä¸€æ ·ï¼Œæœ‰å•¥æ„ä¹‰ï¼Ÿ -->
          <!-- <el-button
              type="primary"
              text
              @click="addMaintain(row)"
          <el-button
            type="primary"
            link
            @click="openDetail(row)"
          >
            æ–°å¢žä¿å…»
          </el-button> -->
            è¯¦æƒ…
          </el-button>
          <el-button
            type="primary"
            link
@@ -218,15 +214,20 @@
    <PlanModal ref="planModalRef" @ok="getTableData" />
        <MaintenanceModal ref="maintainModalRef" @ok="getTableData" />
        <FormDia ref="formDiaRef" @closeDia="getScheduledTableData" />
    <FileListDialog
    <UpkeepDetailModal ref="upkeepDetailModalRef" :java-api="javaApi" />
    <FileListDialog
      ref="fileListDialogRef"
      v-model="fileDialogVisible"
      :show-upload-button="true"
      :show-delete-button="true"
      :title="currentRecordFinished ? '附件(已完结,仅可查看)' : '附件'"
      :show-upload-button="!currentRecordFinished"
      :show-delete-button="!currentRecordFinished"
      :delete-method="handleAttachmentDelete"
      :name-column-label="'附件名称'"
      :rulesRegulationsManagementId="currentMaintenanceTaskId"
      @upload="handleAttachmentUpload" />
      :upload-url="maintenanceFileUploadUrl"
      :upload-data="maintenanceFileUploadData"
      :upload-direct-save="true"
      @upload="refreshFileList"
    />
  </div>
</template>
@@ -236,6 +237,7 @@
import { ElMessage, ElMessageBox } from 'element-plus'
import PlanModal from './Form/PlanModal.vue'
import MaintenanceModal from './Form/MaintenanceModal.vue'
import UpkeepDetailModal from './Form/UpkeepDetailModal.vue'
import FormDia from './Form/formDia.vue'
import FileListDialog from '@/components/Dialog/FileListDialog.vue'
import {
@@ -246,7 +248,6 @@
} from '@/api/equipmentManagement/upkeep'
import {
  listMaintenanceTaskFiles,
  addMaintenanceTaskFile,
  delMaintenanceTaskFile,
} from '@/api/equipmentManagement/maintenanceTaskFile'
import dayjs from 'dayjs'
@@ -260,12 +261,40 @@
const planModalRef = ref()
// ä¿å…»å¼¹çª—控制器
const maintainModalRef = ref()
const upkeepDetailModalRef = ref()
// å®šæ—¶ä»»åŠ¡å¼¹çª—æŽ§åˆ¶å™¨
const formDiaRef = ref()
// é™„件弹窗
const fileListDialogRef = ref(null)
const fileDialogVisible = ref(false)
const currentMaintenanceTaskId = ref(null)
/** å½“前附件所属记录是否已完结(status=1) */
const currentRecordFinished = ref(false)
const javaApi = import.meta.env.VITE_APP_BASE_API
const maintenanceFileUploadUrl = `${javaApi}/maintenanceTaskFile/upload`
const maintenanceFileUploadData = computed(() => ({
  deviceMaintenanceId: currentMaintenanceTaskId.value,
}))
const normalizeMaintenanceFileUrl = (rawUrl = "") => {
  let fileUrl = rawUrl || ""
  if (!fileUrl) return ""
  if (fileUrl.startsWith("http://") || fileUrl.startsWith("https://")) return fileUrl
  if (fileUrl.indexOf("\\") > -1) {
    const lowerPath = fileUrl.toLowerCase()
    const uploadPathIndex = lowerPath.indexOf("uploadpath")
    if (uploadPathIndex > -1) {
      fileUrl = fileUrl.substring(uploadPathIndex).replace(/\\/g, "/")
    } else {
      fileUrl = fileUrl.replace(/\\/g, "/")
    }
  }
  fileUrl = fileUrl.replace(/^\/?uploadPath/i, "/profile")
  if (!fileUrl.startsWith("/")) fileUrl = "/" + fileUrl
  if (!fileUrl.startsWith(javaApi)) fileUrl = javaApi + fileUrl
  return fileUrl
}
// ä»»åŠ¡è®°å½•tab(原设备保养页面)相关变量
const filters = reactive({
@@ -338,6 +367,13 @@
        }
    },
    { prop: "registrant", label: "登记人", minWidth: 100 },
    { prop: "maintenancePerson", label: "保养人", minWidth: 100 },
    {
        prop: "maintenanceItems",
        label: "保养项目",
        minWidth: 180,
        showOverflowTooltip: true,
    },
    { prop: "registrationDate", label: "登记日期", minWidth: 100 },
    {
        fixed: "right",
@@ -365,6 +401,12 @@
        label: "保养项目",
        align: "center",
        prop: "maintenanceLocation",
        showOverflowTooltip: true,
    },
    {
        label: "保养人",
        align: "center",
        prop: "maintenancePerson",
    },
    {
        label: "计划保养日期",
@@ -400,8 +442,8 @@
        label: "保养结果",
        align: "center",
        prop: "maintenanceResult",
        dataType: "slot",
        slot: "maintenanceResultRef",
        minWidth: 200,
        showOverflowTooltip: true,
    },
    {
        label: "状态",
@@ -416,7 +458,7 @@
        dataType: "slot",
        slot: "operation",
        align: "center",
        width: "350px",
    width: "400px",
    },
])
@@ -610,14 +652,13 @@
      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,
      url: normalizeMaintenanceFileUrl(item.fileUrl || item.url),
      raw: item,
    }))
    fileListDialogRef.value?.setList(mapped)
@@ -626,9 +667,14 @@
  }
}
const openDetail = (row) => {
  upkeepDetailModalRef.value?.open(row)
}
// æ‰“开附件弹窗
const openFileDialog = async (row) => {
  currentMaintenanceTaskId.value = row.id
  currentRecordFinished.value = row.status === 1
  fileDialogVisible.value = true
  await fetchMaintenanceTaskFiles(row.id)
}
@@ -639,25 +685,12 @@
  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 (currentRecordFinished.value) {
    ElMessage.warning('该保养已完结,不可删除附件')
    return false
  }
  if (!row?.id) return false
  try {
    await ElMessageBox.confirm('确认删除该附件?', '提示', { type: 'warning' })