已修改7个文件
940 ■■■■■ 文件已修改
src/api/basicData/product.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Upload/FileUpload.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/ImportExcel/index.vue 73 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/attendanceCheckin/index.vue 632 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrder/index.vue 214 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/product.js
@@ -56,3 +56,12 @@
        params: query
    })
}
//  下载产品导入模板
export function downloadProductModelImportTemplate() {
    return request({
        url: '/basic/product/export',
        method: 'get',
        responseType: 'blob'
    })
}
src/components/Upload/FileUpload.vue
@@ -49,9 +49,14 @@
  emits("remove", file);
};
const clearFiles = () => {
  fileList.value = [];
};
defineExpose({
  fileList,
  uploadApi,
  clearFiles,
});
</script>
src/views/basicData/product/ImportExcel/index.vue
@@ -2,16 +2,10 @@
  <el-button type="info" plain icon="Upload" @click="handleImport">
    导入
  </el-button>
  <el-dialog v-model="upload.open" :title="upload.title">
    <FileUpload
      ref="fileUploadRef"
      accept=".xlsx, .xls"
      :headers="upload.headers"
      :action="upload.url + '?updateSupport=' + upload.updateSupport"
      :disabled="upload.isUploading"
      :showTip="false"
      @success="handleFileSuccess"
    />
  <el-dialog v-model="upload.open" :title="upload.title" @close="handleDialogClose">
    <FileUpload ref="fileUploadRef" accept=".xlsx, .xls" :headers="upload.headers" :action="uploadUrl"
      :disabled="upload.isUploading" :showTip="true" @success="handleFileSuccess"
      :downloadTemplate="handleDownloadTemplate" />
    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="submitFileForm">确 定</el-button>
@@ -22,15 +16,19 @@
</template>
<script setup>
import { reactive } from "vue";
import { reactive, computed } from "vue";
import { getToken } from "@/utils/auth.js";
import { FileUpload } from "@/components/Upload";
import { ElMessage } from "element-plus";
import { downloadProductModelImportTemplate } from "@/api/basicData/product.js";
defineOptions({
  name: "产品维护导入",
});
const props = defineProps({
  productId: { type: [String, Number], default: "" },
});
const emits = defineEmits(["uploadSuccess"]);
const fileUploadRef = ref();
const upload = reactive({
@@ -42,17 +40,31 @@
  isUploading: false,
  // 设置上传的请求头部
  headers: { Authorization: "Bearer " + getToken() },
  // 上传的地址
  url: import.meta.env.VITE_APP_BASE_API + "/system/supplier/import",
});
// 上传的地址(携带 productId 参数,传给后端的 importProduct 接口)
const uploadUrl = computed(
  () =>
    import.meta.env.VITE_APP_BASE_API +
    "/basic/product/import" +
    (props.productId ? `?productId=${props.productId}` : "")
);
// 点击导入
const handleImport = () => {
  if (!props.productId) {
    ElMessage({ message: "请先选择产品", type: "warning" });
    return;
  }
  upload.open = true;
  upload.title = "产品导入";
};
const submitFileForm = () => {
  fileUploadRef.value.uploadApi();
};
// 关闭弹窗时清除已选文件
const handleDialogClose = () => {
  fileUploadRef.value?.clearFiles?.();
};
const handleFileSuccess = (response) => {
@@ -65,4 +77,39 @@
    ElMessage({ message: msg, type: "error" });
  }
};
// 下载 Excel 导入模板
const handleDownloadTemplate = () => {
  downloadProductModelImportTemplate()
    .then((blobData) => {
      const blob =
        blobData instanceof Blob
          ? blobData
          : new Blob([blobData], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
      const url = window.URL.createObjectURL(blob);
      const link = document.createElement("a");
      link.href = url;
      link.download = "产品导入模板.xlsx";
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
      window.URL.revokeObjectURL(url);
      ElMessage({ message: "模板下载成功", type: "success" });
    })
    .catch(() => {
      ElMessage({ message: "模板下载失败", type: "error" });
    });
};
</script>
<style scoped>
.import-tip {
  margin-top: 12px;
  font-size: 12px;
  color: var(--el-text-color-secondary);
}
.import-tip .el-button {
  margin-left: 8px;
}
</style>
src/views/basicData/product/index.vue
@@ -73,7 +73,7 @@
        <el-button type="primary" @click="openModelDia('add')">
          新增规格型号
        </el-button>
        <ImportExcel @uploadSuccess="getModelList" />
        <ImportExcel :product-id="currentId" @uploadSuccess="getModelList" />
        <el-button
          type="danger"
          @click="handleDelete"
src/views/personnelManagement/attendanceCheckin/index.vue
@@ -1,7 +1,8 @@
<template>
  <div class="app-container">
    <!-- 员工打卡区 -->
    <el-card shadow="never" class="mb16">
    <el-card shadow="never"
             class="mb16">
      <div class="attendance-header">
        <div>
          <div class="title">打卡签到</div>
@@ -12,12 +13,17 @@
            <div class="label">当前时间</div>
            <div class="value">{{ nowTime }}</div>
          </div>
          <el-button type="primary" size="large" @click="handleCheckInOut" :disabled="todayRecord.workEndAt">
          <el-button type="primary"
                     size="large"
                     @click="handleCheckInOut"
                     :disabled="todayRecord.workEndAt">
            {{ checkInOutText }}
          </el-button>
        </div>
      </div>
      <el-descriptions border :column="4" class="mt10">
      <el-descriptions border
                       :column="4"
                       class="mt10">
        <el-descriptions-item label="员工姓名">
          {{ todayRecord.staffName }}
        </el-descriptions-item>
@@ -28,7 +34,8 @@
          {{ todayRecord.deptName }}
        </el-descriptions-item>
        <el-descriptions-item label="今日状态">
          <el-tag :type="todayStatusTag" size="small">
          <el-tag :type="todayStatusTag"
                  size="small">
            {{ todayStatusText }}
          </el-tag>
        </el-descriptions-item>
@@ -43,370 +50,377 @@
        </el-descriptions-item>
        <el-descriptions-item label="异常标记">
          <span v-if="!todayRecord.id || todayRecord?.status === 0">-</span>
          <el-tag v-else type="danger" size="small">
            {{ todayRecord?.status === 1 ? '迟到' : '早退' }}
          <el-tag v-else
                  type="danger"
                  size="small">
            {{ todayRecord?.status ? getStatusText(todayRecord.status) : '-' }}
          </el-tag>
        </el-descriptions-item>
      </el-descriptions>
    </el-card>
    <div class="attendance-operation">
      <!-- 查询条件(管理员考勤日报) -->
      <el-form :model="searchForm" :inline="true" class="search-form">
        <el-form-item label="部门:" prop="deptId">
          <el-tree-select
              v-model="searchForm.deptId"
              :data="deptOptions"
              :props="{ value: 'id', label: 'label', children: 'children' }"
              value-key="id"
              placeholder="请选择部门"
              check-strictly
              style="width: 200px"
          />
      <el-form :model="searchForm"
               :inline="true"
               class="search-form">
        <el-form-item label="部门:"
                      prop="deptId">
          <el-tree-select v-model="searchForm.deptId"
                          :data="deptOptions"
                          :props="{ value: 'id', label: 'label', children: 'children' }"
                          value-key="id"
                          placeholder="请选择部门"
                          check-strictly
                          style="width: 200px" />
        </el-form-item>
        <el-form-item label="日期:" prop="date">
          <el-date-picker
              v-model="searchForm.date"
              type="date"
              value-format="YYYY-MM-DD"
              format="YYYY-MM-DD"
              placeholder="请选择日期"
              clearable
          />
        <el-form-item label="日期:"
                      prop="date">
          <el-date-picker v-model="searchForm.date"
                          type="date"
                          value-format="YYYY-MM-DD"
                          format="YYYY-MM-DD"
                          placeholder="请选择日期"
                          clearable />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="fetchData">
            <el-icon><Search /></el-icon>
          <el-button type="primary"
                     @click="fetchData">
            <el-icon>
              <Search />
            </el-icon>
            搜索
          </el-button>
          <el-button @click="resetSearch">
            <el-icon><Refresh /></el-icon>
            <el-icon>
              <Refresh />
            </el-icon>
            重置
          </el-button>
        </el-form-item>
      </el-form>
      <el-button icon="Download" @click="handleExport">
      <el-button icon="Download"
                 @click="handleExport">
        导出考勤日报
      </el-button>
    </div>
    <!-- 考勤日报表格 -->
    <div class="table_list">
      <el-table
        :data="tableData"
        border
        v-loading="tableLoading"
        style="width: 100%"
        height="calc(100vh - 24em)"
        :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
        :row-class-name="rowClassName"
      >
        <el-table-column type="index" label="序号" width="60" align="center" />
        <el-table-column
          prop="date"
          label="日期"
          width="120"
        />
        <el-table-column
          prop="deptName"
          label="部门"
          width="140"
        />
        <el-table-column
          prop="staffName"
          label="姓名"
          width="120"
        />
        <el-table-column
          prop="staffNo"
          label="工号"
          width="120"
        />
        <el-table-column
          prop="workStartAt"
          label="上班时间"
          width="140"
        />
        <el-table-column
          prop="workEndAt"
          label="下班时间"
          width="140"
        />
        <el-table-column
          prop="workHours"
          label="工时(小时)"
          align="center"
        />
        <el-table-column
          prop="status"
          label="考勤状态"
          align="center"
        >
      <el-table :data="tableData"
                border
                v-loading="tableLoading"
                style="width: 100%"
                height="calc(100vh - 24em)"
                :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
                :row-class-name="rowClassName">
        <el-table-column type="index"
                         label="序号"
                         width="60"
                         align="center" />
        <el-table-column prop="date"
                         label="日期"
                         width="120" />
        <el-table-column prop="deptName"
                         label="部门"
                         width="140" />
        <el-table-column prop="staffName"
                         label="姓名"
                         width="120" />
        <el-table-column prop="staffNo"
                         label="工号"
                         width="120" />
        <el-table-column prop="workStartAt"
                         label="上班时间"
                         width="140" />
        <el-table-column prop="workEndAt"
                         label="下班时间"
                         width="140" />
        <el-table-column prop="workHours"
                         label="工时(小时)"
                         align="center" />
        <el-table-column prop="status"
                         label="考勤状态"
                         align="center">
          <template #default="scope">
            <el-tag
              v-if="scope.row.status === 0"
              type="success"
              size="small"
            >
            <el-tag v-if="scope.row.status === 0"
                    type="success"
                    size="small">
              正常
            </el-tag>
            <el-tag
              v-else
              type="danger"
              size="small"
            >
              {{ scope.row.status === 1 ? '迟到' : '早退' }}
            <el-tag v-else
                    type="danger"
                    size="small">
              <!-- {{ scope.row.status === 1 ? '迟到' : scope.row.status === 2 ? '早退' : '迟到、早退' }} -->
              {{ getStatusText(scope.row.status) }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column
          prop="remark"
          label="备注"
          show-overflow-tooltip
        />
        <el-table-column prop="remark"
                         label="备注"
                         show-overflow-tooltip />
      </el-table>
      <pagination :total="total" layout="total, sizes, prev, pager, next, jumper"
                  :page="page.current" :limit="page.size" @pagination="paginationChange" />
      <pagination :total="total"
                  layout="total, sizes, prev, pager, next, jumper"
                  :page="page.current"
                  :limit="page.size"
                  @pagination="paginationChange" />
    </div>
  </div>
</template>
<script setup>
import { ref, reactive, computed, onMounted, onBeforeUnmount } from "vue";
import {ElMessage, ElMessageBox} from "element-plus";
import {
  createPersonalAttendanceRecord,
  findPersonalAttendanceRecords, findTodayPersonalAttendanceRecord
} from "@/api/personnelManagement/personalAttendanceRecords.js";
import Pagination from "@/components/Pagination/index.vue";
import {deptTreeSelect} from "@/api/system/user.js";
import {Refresh, Search} from "@element-plus/icons-vue";
  import { ref, reactive, computed, onMounted, onBeforeUnmount } from "vue";
  import { ElMessage, ElMessageBox } from "element-plus";
  import {
    createPersonalAttendanceRecord,
    findPersonalAttendanceRecords,
    findTodayPersonalAttendanceRecord,
  } from "@/api/personnelManagement/personalAttendanceRecords.js";
  import Pagination from "@/components/Pagination/index.vue";
  import { deptTreeSelect } from "@/api/system/user.js";
  import { Refresh, Search } from "@element-plus/icons-vue";
const { proxy } = getCurrentInstance()
const tableLoading = ref(false)
// 分页参数
const page = reactive({
  current: 1,
  size: 10,
  total: 0
})
// 今日数据
const todayRecord = ref({})
// 部门选项
const deptOptions = ref([])
// 查询表单
const searchForm = reactive({
  deptId: "",
  date: "",
});
// 表格数据
const tableData = ref([]);
// 当前时间展示
const nowTime = ref("");
let timer = null;
const updateNowTime = () => {
  const now = new Date();
  const Y = now.getFullYear();
  const M = String(now.getMonth() + 1).padStart(2, "0");
  const D = String(now.getDate()).padStart(2, "0");
  const h = String(now.getHours()).padStart(2, "0");
  const m = String(now.getMinutes()).padStart(2, "0");
  const s = String(now.getSeconds()).padStart(2, "0");
  nowTime.value = `${Y}-${M}-${D} ${h}:${m}:${s}`;
};
// 打卡按钮文本
const checkInOutText = computed(() => {
  if (!todayRecord.value || !todayRecord.value.workStartAt) {
    return "上班打卡";
  }
  if (!todayRecord.value.workEndAt) {
    return "下班打卡";
  }
  return "今日已打卡完成";
});
// 今日状态展示
const todayStatusTag = computed(() => {
  if (!todayRecord.value.id) return "info";
  if (todayRecord.value.status === 0) return "success";
  return "danger";
});
const todayStatusText = computed(() => {
  if (!todayRecord.value.id) return "未打卡";
  switch (todayRecord.value.status) {
    case 0:
      return "正常"
    case 1:
      return "迟到"
    case 2:
      return "早退"
  }
});
// 行样式:异常高亮
const rowClassName = ({ row }) => {
  if (row.status === 1 || row.status === 2) {
    return "row-abnormal";
  }
  return "";
};
// 查询部门列表
const fetchDeptOptions = () => {
  deptTreeSelect().then(response => {
    deptOptions.value = filterDisabledDept(JSON.parse(JSON.stringify(response.data)))
  })
}
/** 过滤禁用的部门 */
function filterDisabledDept(deptList) {
  return deptList.filter(dept => {
    if (dept.disabled) {
      return false
    }
    if (dept.children && dept.children.length) {
      dept.children = filterDisabledDept(dept.children)
    }
    return true
  })
}
// 查询
const fetchData = () => {
  tableLoading.value = true
  findPersonalAttendanceRecords({...page, ...searchForm}).then((res) => {
    tableData.value = res.data.records;
    page.value.total = res.data.total;
  }).finally(() => {
    tableLoading.value = false;
  const { proxy } = getCurrentInstance();
  const tableLoading = ref(false);
  // 分页参数
  const page = reactive({
    current: 1,
    size: 10,
    total: 0,
  });
};
  // 今日数据
  const todayRecord = ref({});
// 查询今日打卡信息
const fetchTodayData = () => {
  findTodayPersonalAttendanceRecord({}).then((res) => {
    todayRecord.value = res.data;
  })
};
  // 部门选项
  const deptOptions = ref([]);
const paginationChange = (pagination) => {
  page.current = pagination.page;
  page.size = pagination.limit;
  fetchData();
}
  // 查询表单
  const searchForm = reactive({
    deptId: "",
    date: "",
  });
const resetSearch = () => {
  searchForm.deptId = "";
  searchForm.date = "";
  fetchData();
};
  // 表格数据
  const tableData = ref([]);
const handleExport = () => {
  ElMessageBox.confirm("是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
  // 当前时间展示
  const nowTime = ref("");
  let timer = null;
  const updateNowTime = () => {
    const now = new Date();
    const Y = now.getFullYear();
    const M = String(now.getMonth() + 1).padStart(2, "0");
    const D = String(now.getDate()).padStart(2, "0");
    const h = String(now.getHours()).padStart(2, "0");
    const m = String(now.getMinutes()).padStart(2, "0");
    const s = String(now.getSeconds()).padStart(2, "0");
    nowTime.value = `${Y}-${M}-${D} ${h}:${m}:${s}`;
  };
  // 打卡按钮文本
  const checkInOutText = computed(() => {
    if (!todayRecord.value || !todayRecord.value.workStartAt) {
      return "上班打卡";
    }
    if (!todayRecord.value.workEndAt) {
      return "下班打卡";
    }
    return "今日已打卡完成";
  });
  // 今日状态展示
  const todayStatusTag = computed(() => {
    if (!todayRecord.value.id) return "info";
    if (todayRecord.value.status === 0) return "success";
    return "danger";
  });
  const getStatusText = status => {
    switch (status) {
      case 0:
        return "正常";
      case 1:
        return "迟到";
      case 2:
        return "早退";
      case 3:
        return "迟到、早退";
      case 4:
        return "缺勤";
    }
  };
  const todayStatusText = computed(() => {
    if (!todayRecord.value.id) return "未打卡";
    switch (todayRecord.value.status) {
      case 0:
        return "正常";
      case 1:
        return "迟到";
      case 2:
        return "早退";
      case 3:
        return "迟到、早退";
      case 4:
        return "缺勤";
    }
  });
  // 行样式:异常高亮
  const rowClassName = ({ row }) => {
    if (row.status === 1 || row.status === 2) {
      return "row-abnormal";
    }
    return "";
  };
  // 查询部门列表
  const fetchDeptOptions = () => {
    deptTreeSelect().then(response => {
      deptOptions.value = filterDisabledDept(
        JSON.parse(JSON.stringify(response.data))
      );
    });
  };
  /** 过滤禁用的部门 */
  function filterDisabledDept(deptList) {
    return deptList.filter(dept => {
      if (dept.disabled) {
        return false;
      }
      if (dept.children && dept.children.length) {
        dept.children = filterDisabledDept(dept.children);
      }
      return true;
    });
  }
  // 查询
  const fetchData = () => {
    tableLoading.value = true;
    findPersonalAttendanceRecords({ ...page, ...searchForm })
      .then(res => {
        tableData.value = res.data.records;
        page.value.total = res.data.total;
      })
      .finally(() => {
        tableLoading.value = false;
      });
  };
  // 查询今日打卡信息
  const fetchTodayData = () => {
    findTodayPersonalAttendanceRecord({}).then(res => {
      todayRecord.value = res.data;
    });
  };
  const paginationChange = pagination => {
    page.current = pagination.page;
    page.size = pagination.limit;
    fetchData();
  };
  const resetSearch = () => {
    searchForm.deptId = "";
    searchForm.date = "";
    fetchData();
  };
  const handleExport = () => {
    ElMessageBox.confirm("是否确认导出?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        proxy.download("/personalAttendanceRecords/export", {}, "考勤记录.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
  };
// 打卡
const handleCheckInOut = () => {
  createPersonalAttendanceRecord({}).then((res) => {
    fetchData()
    fetchTodayData()
    ElMessage.success("打卡成功!");
  })
};
  // 打卡
  const handleCheckInOut = () => {
    createPersonalAttendanceRecord({}).then(res => {
      fetchData();
      fetchTodayData();
      ElMessage.success("打卡成功!");
    });
  };
onMounted(() => {
  updateNowTime();
  timer = setInterval(updateNowTime, 1000);
  // 默认展示当天数据
  const today = new Date();
  const Y = today.getFullYear();
  const M = String(today.getMonth() + 1).padStart(2, "0");
  const D = String(today.getDate()).padStart(2, "0");
  searchForm.date = `${Y}-${M}-${D}`;
  fetchData();
  fetchTodayData()
  fetchDeptOptions();
});
  onMounted(() => {
    updateNowTime();
    timer = setInterval(updateNowTime, 1000);
    // 默认展示当天数据
    const today = new Date();
    const Y = today.getFullYear();
    const M = String(today.getMonth() + 1).padStart(2, "0");
    const D = String(today.getDate()).padStart(2, "0");
    searchForm.date = `${Y}-${M}-${D}`;
    fetchData();
    fetchTodayData();
    fetchDeptOptions();
  });
onBeforeUnmount(() => {
  if (timer) {
    clearInterval(timer);
  }
});
  onBeforeUnmount(() => {
    if (timer) {
      clearInterval(timer);
    }
  });
</script>
<style scoped lang="scss">
.mb16 {
  margin-bottom: 16px;
}
  .mb16 {
    margin-bottom: 16px;
  }
.attendance-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
  .attendance-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
.attendance-header .title {
  font-size: 18px;
  font-weight: 600;
  margin-bottom: 4px;
}
  .attendance-header .title {
    font-size: 18px;
    font-weight: 600;
    margin-bottom: 4px;
  }
.attendance-header .sub-title {
  font-size: 13px;
  color: #909399;
}
  .attendance-header .sub-title {
    font-size: 13px;
    color: #909399;
  }
.attendance-actions {
  display: flex;
  align-items: center;
  gap: 16px;
}
  .attendance-actions {
    display: flex;
    align-items: center;
    gap: 16px;
  }
.time-block {
  text-align: right;
}
  .time-block {
    text-align: right;
  }
.time-block .label {
  font-size: 12px;
  color: #909399;
}
  .time-block .label {
    font-size: 12px;
    color: #909399;
  }
.time-block .value {
  font-size: 18px;
  font-weight: 600;
  color: #333;
}
  .time-block .value {
    font-size: 18px;
    font-weight: 600;
    color: #333;
  }
::v-deep(.row-abnormal) {
  background-color: #fff5f5;
}
  ::v-deep(.row-abnormal) {
    background-color: #fff5f5;
  }
.attendance-operation {
  display: flex;
  justify-content: space-between;
}
  .attendance-operation {
    display: flex;
    justify-content: space-between;
  }
</style>
src/views/productionManagement/productionReporting/index.vue
@@ -163,6 +163,11 @@
      width: 120,
    },
    {
      label: "工序",
      prop: "process",
      width: 120,
    },
    {
      label: "工单编号",
      prop: "workOrderNo",
      width: 120,
src/views/productionManagement/workOrder/index.vue
@@ -166,26 +166,32 @@
    <el-dialog v-model="reportDialogVisible"
               title="报工"
               width="500px">
      <el-form :model="reportForm"
      <el-form ref="reportFormRef"
               :model="reportForm"
               :rules="reportFormRules"
               label-width="120px">
        <el-form-item label="待生产数量">
          <el-input v-model="reportForm.planQuantity"
                    readonly
                    style="width: 300px" />
        </el-form-item>
        <el-form-item label="本次生产数量">
        <el-form-item label="本次生产数量" prop="quantity">
          <el-input v-model.number="reportForm.quantity"
                    type="number"
                    min="1"
                    step="1"
                    style="width: 300px"
                    placeholder="请输入本次生产数量" />
                    placeholder="请输入本次生产数量"
                    @input="handleQuantityInput" />
        </el-form-item>
        <el-form-item label="报废数量">
        <el-form-item label="报废数量" prop="scrapQty">
          <el-input v-model.number="reportForm.scrapQty"
                    type="number"
                    min="1"
                    min="0"
                    step="1"
                    style="width: 300px"
                    placeholder="请输入报废数量" />
                    placeholder="请输入报废数量"
                    @input="handleScrapQtyInput" />
        </el-form-item>
        <el-form-item label="班组信息">
          <el-select v-model="reportForm.userId"
@@ -214,7 +220,7 @@
</template>
<script setup>
  import { onMounted, ref } from "vue";
  import { onMounted, ref, nextTick } from "vue";
  import { ElMessageBox } from "element-plus";
  import dayjs from "dayjs";
  import {
@@ -345,10 +351,12 @@
  const transferCardRowData = ref(null);
  const reportDialogVisible = ref(false);
  const workOrderFilesRef = ref(null);
  const reportFormRef = ref(null);
  const userOptions = ref([]);
  const reportForm = reactive({
    planQuantity: 0,
    quantity: 0,
    quantity: null,
    scrapQty: null,
    userName: "",
    workOrderId: "",
    reportWork: "",
@@ -356,6 +364,100 @@
    userId: "",
    productMainId: null,
  });
  // 本次生产数量验证规则
  const validateQuantity = (rule, value, callback) => {
    if (value === null || value === undefined || value === '') {
      callback(new Error('请输入本次生产数量'));
      return;
    }
    const num = Number(value);
    // 整数且大于等于1
    if (isNaN(num) || !Number.isInteger(num) || num < 1) {
      callback(new Error('本次生产数量必须大于等于1'));
      return;
    }
    callback();
  };
  // 报废数量验证规则
  const validateScrapQty = (rule, value, callback) => {
    if (value === null || value === undefined || value === '') {
      callback();
      return;
    }
    const num = Number(value);
    // 整数且大于等于0
    if (isNaN(num) || !Number.isInteger(num) || num < 0) {
      callback(new Error('报废数量必须大于等于0'));
      return;
    }
    callback();
  };
  // 验证规则
  const reportFormRules = {
    quantity: [
      { required: true, validator: validateQuantity, trigger: 'blur' }
    ],
    scrapQty: [
      { validator: validateScrapQty, trigger: 'blur' }
    ]
  };
  // 处理本次生产数量输入,限制必须大于等于1
  const handleQuantityInput = (value) => {
    if (value === '' || value === null || value === undefined) {
      reportForm.quantity = null;
      return;
    }
    const num = Number(value);
    if (isNaN(num)) {
      return;
    }
    // 如果小于1,清除
    if (num < 1) {
      reportForm.quantity = null;
      return;
    }
    // 如果是小数取整数部分
    if (!Number.isInteger(num)) {
      const intValue = Math.floor(num);
      // 如果取整后小于1,清除
      if (intValue < 1) {
        reportForm.quantity = null;
        return;
      }
      reportForm.quantity = intValue;
      return;
    }
    reportForm.quantity = num;
  };
  // 处理报废数量
  const handleScrapQtyInput = (value) => {
    if (value === '' || value === null || value === undefined) {
      reportForm.scrapQty = null;
      return;
    }
    const num = Number(value);
    // 如果是NaN,保持原值
    if (isNaN(num)) {
      return;
    }
    // 如果是负数,清除输入
    if (num < 0) {
      reportForm.scrapQty = null;
      return;
    }
    // 如果是小数,取整数部分
    if (!Number.isInteger(num)) {
      reportForm.scrapQty = Math.floor(num);
      return;
    }
    // 有效的非负整数(包括0)
    reportForm.scrapQty = num;
  };
  const currentReportRowData = ref(null);
  const page = reactive({
    current: 1,
@@ -494,12 +596,15 @@
  const showReportDialog = row => {
    currentReportRowData.value = row;
    reportForm.planQuantity = row.planQuantity;
    reportForm.quantity = row.quantity;
    reportForm.quantity = row.quantity !== undefined && row.quantity !== null ? row.quantity : null;
    reportForm.productProcessRouteItemId = row.productProcessRouteItemId;
    reportForm.workOrderId = row.id;
    reportForm.reportWork = row.reportWork;
    reportForm.productMainId = row.productMainId;
    reportForm.scrapQty = row.scrapQty;
    reportForm.scrapQty = row.scrapQty !== undefined && row.scrapQty !== null ? row.scrapQty : null;
    nextTick(() => {
      reportFormRef.value?.clearValidate();
    });
    // 获取当前登录用户信息,设置为默认选中
    getUserProfile()
      .then(res => {
@@ -520,35 +625,72 @@
  };
  const handleReport = () => {
    if (reportForm.planQuantity <= 0) {
      ElMessageBox.alert("待生产数量为0,无法报工", "提示", {
        confirmButtonText: "确定",
      });
      return;
    }
    if (!reportForm.quantity || reportForm.quantity <= 0) {
      ElMessageBox.alert("请输入有效的本次生产数量", "提示", {
        confirmButtonText: "确定",
      });
      return;
    }
    if (reportForm.quantity > reportForm.planQuantity) {
      ElMessageBox.alert("本次生产数量不能超过待生产数量", "提示", {
        confirmButtonText: "确定",
      });
      return;
    }
    // console.log(reportForm);
    addProductMain(reportForm).then(res => {
      if (res.code === 200) {
        proxy.$modal.msgSuccess("报工成功");
        reportDialogVisible.value = false;
        getList();
      } else {
        ElMessageBox.alert(res.msg || "报工失败", "提示", {
    reportFormRef.value?.validate((valid) => {
      if (!valid) {
        return false;
      }
      if (reportForm.planQuantity <= 0) {
        ElMessageBox.alert("待生产数量为0,无法报工", "提示", {
          confirmButtonText: "确定",
        });
        return;
      }
      // 验证本次生产数量
      if (reportForm.quantity === null || reportForm.quantity === undefined || reportForm.quantity === '') {
        ElMessageBox.alert("请输入本次生产数量", "提示", {
          confirmButtonText: "确定",
        });
        return;
      }
      const quantity = Number(reportForm.quantity);
      const scrapQty = reportForm.scrapQty === null || reportForm.scrapQty === undefined || reportForm.scrapQty === ''
        ? 0
        : Number(reportForm.scrapQty);
      // 本次生产数量
      if (isNaN(quantity) || !Number.isInteger(quantity) || quantity < 1) {
        ElMessageBox.alert("本次生产数量必须大于等于1", "提示", {
          confirmButtonText: "确定",
        });
        return;
      }
      // 报废数量必须是整数且大于等于0
      if (isNaN(scrapQty) || !Number.isInteger(scrapQty) || scrapQty < 0) {
        ElMessageBox.alert("报废数量必须大于等于0", "提示", {
          confirmButtonText: "确定",
        });
        return;
      }
      if (quantity > reportForm.planQuantity) {
        ElMessageBox.alert("本次生产数量不能超过待生产数量", "提示", {
          confirmButtonText: "确定",
        });
        return;
      }
      const submitData = {
        ...reportForm,
        quantity: quantity,
        scrapQty: scrapQty
      };
      // console.log(submitData);
      addProductMain(submitData).then(res => {
        if (res.code === 200) {
          proxy.$modal.msgSuccess("报工成功");
          reportDialogVisible.value = false;
          getList();
        } else {
          ElMessageBox.alert(res.msg || "报工失败", "提示", {
            confirmButtonText: "确定",
          });
        }
      });
    });
  };