已添加3个文件
已修改14个文件
1773 ■■■■ 文件已修改
src/api/basicData/product.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/monthlyStatistics.js 65 ●●●●● 补丁 | 查看 | 原始文档 | 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/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/attendanceCheckin/index.vue 637 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/monthlyStatistics/components/formDia.vue 318 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/monthlyStatistics/index.vue 303 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrder/index.vue 214 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/formDia.vue 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/nonconformingManagement/components/formDia.vue 49 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/nonconformingManagement/components/inspectionFormDia.vue 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/nonconformingManagement/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/components/formDia.vue 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue 26 ●●●● 补丁 | 查看 | 原始文档 | 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/api/personnelManagement/monthlyStatistics.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
import request from "@/utils/request";
// äººå‘˜è–ªèµ„台账列表
export function monthlyStatisticsListPage(query) {
  return request({
    url: "/compensationPerformance/listPage",
    method: "get",
    params: query,
  });
}
// äººå‘˜è–ªèµ„台账详情
export function monthlyStatisticsGet(id) {
  return request({
    url: "/monthlyStatistics/get",
    method: "get",
    params: { id },
  });
}
// æ–°å¢žäººå‘˜è–ªèµ„台账
export function monthlyStatisticsAdd(data) {
  return request({
    url: "/compensationPerformance/add",
    method: "post",
    data,
  });
}
// ç¼–辑人员薪资台账
export function monthlyStatisticsUpdate(data) {
  return request({
    url: "/compensationPerformance/update",
    method: "post",
    data,
  });
}
// åˆ é™¤äººå‘˜è–ªèµ„台账
export function monthlyStatisticsDelete(ids) {
  return request({
    url: "/compensationPerformance/delete",
    method: "delete",
    data: ids,
  });
}
// å¯¼å‡ºäººå‘˜è–ªèµ„台账
export function monthlyStatisticsExport(query) {
  return request({
    url: "/compensationPerformance/export",
    method: "get",
    params: query,
    responseType: "blob",
  });
}
// äººå‘˜åˆ—表
export function staffOnJobList(query) {
  return request({
    url: "/staff/staffOnJob/list",
    method: "get",
    params: query,
  });
}
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/index.vue
@@ -132,17 +132,17 @@
            <div class="process-card">
              <div class="process-card__label">累计总投入</div>
              <div class="process-card__value">{{ formatAmount(processAside.totalInput) }}<span class="unit">元</span>
              <div class="process-card__value">{{ formatAmount(processAside.totalInput) }}
              </div>
            </div>
            <div class="process-card">
              <div class="process-card__label">累计总报废</div>
              <div class="process-card__value">{{ formatAmount(processAside.totalScrap) }}<span class="unit">元</span>
              <div class="process-card__value">{{ formatAmount(processAside.totalScrap) }}
              </div>
            </div>
            <div class="process-card">
              <div class="process-card__label">累计总产出</div>
              <div class="process-card__value">{{ formatAmount(processAside.totalOutput) }}<span class="unit">元</span>
              <div class="process-card__value">{{ formatAmount(processAside.totalOutput) }}
              </div>
            </div>
          </div>
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>
@@ -42,334 +49,378 @@
          {{ todayRecord?.workHours ?? '-' }}
        </el-descriptions-item>
        <el-descriptions-item label="异常标记">
          <span v-if="todayRecord?.status === 'normal'">-</span>
          <el-tag v-else type="danger" size="small">
            {{ todayRecord?.statusText }}
          <span v-if="!todayRecord.id || todayRecord?.status === 0">-</span>
          <el-tag v-else
                  type="danger"
                  size="small">
            {{ todayRecord?.status ? getStatusText(todayRecord.status) : '-' }}
          </el-tag>
        </el-descriptions-item>
      </el-descriptions>
    </el-card>
    <!-- æŸ¥è¯¢æ¡ä»¶ï¼ˆç®¡ç†å‘˜è€ƒå‹¤æ—¥æŠ¥ï¼‰ -->
    <div class="search_form">
      <div>
        <span class="search_title">部门:</span>
        <el-select
          v-model="searchForm.deptId"
          placeholder="请选择部门"
          style="width: 180px"
          clearable
        >
          <el-option
            v-for="item in deptOptions"
            :key="item.value"
            :label="item.label"
            :value="item.value"
          />
        </el-select>
        <span class="search_title ml10">日期:</span>
        <el-date-picker
          v-model="searchForm.date"
          type="date"
          value-format="YYYY-MM-DD"
          format="YYYY-MM-DD"
          placeholder="请选择日期"
          clearable
        />
        <el-button type="primary" @click="fetchData" style="margin-left: 10px">
          æœç´¢
        </el-button>
        <el-button @click="resetSearch">重置</el-button>
      </div>
      <div>
        <el-button icon="Download" @click="handleExport">
          å¯¼å‡ºè€ƒå‹¤æ—¥æŠ¥
        </el-button>
      </div>
    <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-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>
        <el-form-item>
          <el-button type="primary"
                     @click="fetchData">
            <el-icon>
              <Search />
            </el-icon>
            æœç´¢
          </el-button>
          <el-button @click="resetSearch">
            <el-icon>
              <Refresh />
            </el-icon>
            é‡ç½®
          </el-button>
        </el-form-item>
      </el-form>
      <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 } from "element-plus";
import {
  createPersonalAttendanceRecord,
  findPersonalAttendanceRecords, findTodayPersonalAttendanceRecord
} from "@/api/personnelManagement/personalAttendanceRecords.js";
import Pagination from "@/components/Pagination/index.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 tableLoading = ref(false)
// åˆ†é¡µå‚æ•°
const page = reactive({
  current: 1,
  size: 10,
  total: 0
})
// ä»Šæ—¥æ•°æ®
const todayRecord = ref({})
// éƒ¨é—¨é€‰é¡¹
const deptOptions = [
  { label: "生产一部", value: "生产一部" },
  { label: "生产二部", value: "生产二部" },
  { label: "设备维护部", value: "设备维护部" },
  { label: "质检部", value: "质检部" },
];
// æŸ¥è¯¢è¡¨å•
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 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 = () => {
  ElMessage.success("当前为演示页面,导出功能未对接实际接口");
};
  // å½“前时间展示
  const nowTime = ref("");
  let timer = null;
// æ‰“卡
const handleCheckInOut = () => {
  createPersonalAttendanceRecord({}).then((res) => {
    fetchData()
    fetchTodayData()
    ElMessage.success("打卡成功!");
  })
};
  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}`;
  };
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()
});
  // æ‰“卡按钮文本
  const checkInOutText = computed(() => {
    if (!todayRecord.value || !todayRecord.value.workStartAt) {
      return "上班打卡";
    }
    if (!todayRecord.value.workEndAt) {
      return "下班打卡";
    }
    return "今日已打卡完成";
  });
onBeforeUnmount(() => {
  if (timer) {
    clearInterval(timer);
  // ä»Šæ—¥çŠ¶æ€å±•ç¤º
  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("打卡成功!");
    });
  };
  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);
    }
  });
</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;
  }
</style>
src/views/personnelManagement/monthlyStatistics/components/formDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,318 @@
<template>
  <el-dialog v-model="dialogVisible"
             :title="title"
             width="700px"
             :close-on-click-modal="false">
    <el-form ref="formRef"
             :model="form"
             :rules="rules"
             label-width="140px"
             label-position="top">
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="统计月份"
                        prop="payDate">
            <el-date-picker v-model="form.payDate"
                            type="month"
                            value-format="YYYY-MM"
                            format="YYYY-MM"
                            placeholder="请选择月份"
                            style="width: 100%"
                            :disabled="operationType === 'view'" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="姓名"
                        prop="staffId">
            <el-select v-model="form.staffId"
                       placeholder="请选择员工"
                       style="width: 100%"
                       :disabled="operationType === 'view'">
              <el-option v-for="item in userList"
                         :key="item.id"
                         :label="item.staffName"
                         :value="item.id" />
            </el-select>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="基本工资"
                        prop="basicSalary">
            <el-input v-model="form.basicSalary"
                      type="number"
                      placeholder="请输入基本工资"
                      :disabled="operationType === 'view'" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="计件工资"
                        prop="pieceworkSalary">
            <el-input v-model="form.pieceworkSalary"
                      type="number"
                      placeholder="请输入计件工资"
                      :disabled="operationType === 'view'" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="计时工资"
                        prop="hourlySalary">
            <el-input v-model="form.hourlySalary"
                      type="number"
                      placeholder="请输入计时工资"
                      :disabled="operationType === 'view'" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="其他收入"
                        prop="otherIncome">
            <el-input v-model="form.otherIncome"
                      type="number"
                      placeholder="请输入其他收入"
                      :disabled="operationType === 'view'" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="社保个人"
                        prop="socialSecurityIndividuals">
            <el-input v-model="form.socialSecurityIndividuals"
                      type="number"
                      placeholder="请输入社保个人"
                      :disabled="operationType === 'view'" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="公积金个人"
                        prop="providentFundIndividuals">
            <el-input v-model="form.providentFundIndividuals"
                      type="number"
                      placeholder="请输入公积金个人"
                      :disabled="operationType === 'view'" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="个人所得税"
                        prop="personalIncomeTax">
            <el-input v-model="form.personalIncomeTax"
                      type="number"
                      placeholder="请输入个人所得税"
                      :disabled="operationType === 'view'" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="其他扣款"
                        prop="otherDeductions">
            <el-input v-model="form.otherDeductions"
                      type="number"
                      placeholder="请输入其他扣款"
                      :disabled="operationType === 'view'" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="24">
          <el-form-item label="备注"
                        prop="remark">
            <el-input v-model="form.remark"
                      type="textarea"
                      placeholder="请输入备注"
                      :rows="3"
                      :disabled="operationType === 'view'" />
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary"
                   @click="submitForm"
                   v-if="operationType !== 'view'">
          ç¡®å®š
        </el-button>
      </span>
    </template>
  </el-dialog>
</template>
<script setup>
  import { ref, reactive, computed, onMounted } from "vue";
  import { ElMessage } from "element-plus";
  import {
    monthlyStatisticsAdd,
    monthlyStatisticsUpdate,
    staffOnJobList,
  } from "@/api/personnelManagement/monthlyStatistics.js";
  const props = defineProps({
    modelValue: {
      type: Boolean,
      default: false,
    },
    operationType: {
      type: String,
      default: "add",
    },
    row: {
      type: Object,
      default: () => ({}),
    },
  });
  const emit = defineEmits(["update:modelValue", "close"]);
  const dialogVisible = computed({
    get: () => props.modelValue,
    set: val => emit("update:modelValue", val),
  });
  const title = computed(() => {
    if (props.operationType === "add") return "新增薪资台账";
    if (props.operationType === "edit") return "编辑薪资台账";
    return "查看薪资台账";
  });
  const formRef = ref();
  const form = reactive({
    id: "",
    payDate: "",
    staffId: "",
    basicSalary: 0,
    pieceworkSalary: 0,
    hourlySalary: 0,
    otherIncome: 0,
    socialSecurityIndividuals: 0,
    providentFundIndividuals: 0,
    personalIncomeTax: 0,
    otherDeductions: 0,
    payableWages: 0,
    deductibleWages: 0,
    actualWages: 0,
    remark: "",
  });
  const rules = {
    payDate: [{ required: true, message: "请选择统计月份", trigger: "change" }],
    staffId: [{ required: true, message: "请选择员工", trigger: "change" }],
    basicSalary: [{ required: true, message: "请输入基本工资", trigger: "blur" }],
  };
  const userList = ref([]);
  const loadUserList = () => {
    // userListNoPage().then(res => {
    //   userList.value = res.data || [];
    // });
    staffOnJobList().then(res => {
      userList.value = res.data || [];
    });
  };
  const openDialog = (type, row) => {
    // é‡ç½®è¡¨å•
    Object.assign(form, {
      id: "",
      payDate: "",
      staffId: "",
      basicSalary: 0,
      pieceworkSalary: 0,
      hourlySalary: 0,
      otherIncome: 0,
      socialSecurityIndividuals: 0,
      providentFundIndividuals: 0,
      personalIncomeTax: 0,
      otherDeductions: 0,
      payableWages: 0,
      deductibleWages: 0,
      actualWages: 0,
      remark: "",
    });
    if (type === "add") {
      dialogVisible.value = true;
    } else if (type === "edit" || type === "view") {
      if (row && row.id) {
        Object.assign(form, row);
        dialogVisible.value = true;
      }
    }
  };
  const submitForm = () => {
    formRef.value.validate(valid => {
      if (valid) {
        form.basicSalary = Number(form.basicSalary);
        form.pieceworkSalary = Number(form.pieceworkSalary);
        form.hourlySalary = Number(form.hourlySalary);
        form.otherIncome = Number(form.otherIncome);
        form.socialSecurityIndividuals = Number(form.socialSecurityIndividuals);
        form.providentFundIndividuals = Number(form.providentFundIndividuals);
        form.personalIncomeTax = Number(form.personalIncomeTax);
        form.otherDeductions = Number(form.otherDeductions);
        // è®¡ç®—应发工资、应扣工资和实发工资
        const payableWages =
          form.basicSalary +
          form.pieceworkSalary +
          form.hourlySalary +
          form.otherIncome;
        const deductibleWages =
          form.socialSecurityIndividuals +
          form.providentFundIndividuals +
          form.personalIncomeTax +
          form.otherDeductions;
        const actualWages = payableWages - deductibleWages;
        const submitData = {
          ...form,
          payableWages,
          deductibleWages,
          actualWages,
        };
        if (props.operationType === "add") {
          monthlyStatisticsAdd(submitData).then(res => {
            if (res.code === 200) {
              ElMessage.success("新增成功");
              dialogVisible.value = false;
              emit("close");
            } else {
              ElMessage.error(res.msg || "新增失败");
            }
          });
        } else if (props.operationType === "edit") {
          monthlyStatisticsUpdate(submitData).then(res => {
            if (res.code === 200) {
              ElMessage.success("更新成功");
              dialogVisible.value = false;
              emit("close");
            } else {
              ElMessage.error(res.msg || "更新失败");
            }
          });
        }
      }
    });
  };
  onMounted(() => {
    loadUserList();
  });
  defineExpose({
    openDialog,
  });
</script>
<style scoped>
  .dialog-footer {
    text-align: right;
  }
</style>
src/views/personnelManagement/monthlyStatistics/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,303 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title">姓名:</span>
        <el-input v-model="searchForm.staffName"
                  style="width: 240px"
                  placeholder="请输入姓名搜索"
                  @change="handleQuery"
                  clearable
                  :prefix-icon="Search" />
        <span class="search_title ml10">月份:</span>
        <el-date-picker v-model="searchForm.payDateStr"
                        type="month"
                        @change="handleQuery"
                        value-format="YYYY-MM"
                        format="YYYY-MM"
                        placeholder="请选择月份"
                        style="width: 240px"
                        clearable />
        <el-button type="primary"
                   @click="handleQuery"
                   style="margin-left: 10px">
          æœç´¢
        </el-button>
      </div>
      <div>
        <el-button @click="handleExport"
                   style="margin-right: 10px">导出</el-button>
        <el-button type="primary"
                   @click="openForm('add')">新增台账</el-button>
        <el-button type="danger"
                   plain
                   @click="handleDelete">删除</el-button>
      </div>
    </div>
    <div class="table_list">
      <PIMTable rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :isSelection="true"
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
                :total="page.total"></PIMTable>
    </div>
    <form-dia v-model="dialogVisible"
              :operation-type="operationType"
              :row="currentRow"
              ref="formDia"
              @close="handleQuery"></form-dia>
  </div>
</template>
<script setup>
  import { Search } from "@element-plus/icons-vue";
  import {
    onMounted,
    ref,
    reactive,
    toRefs,
    getCurrentInstance,
    nextTick,
  } from "vue";
  import { ElMessageBox } from "element-plus";
  import dayjs from "dayjs";
  import FormDia from "./components/formDia.vue";
  import {
    monthlyStatisticsListPage,
    monthlyStatisticsDelete,
  } from "@/api/personnelManagement/monthlyStatistics.js";
  const data = reactive({
    searchForm: {
      staffName: "",
      payDateStr: "",
    },
  });
  const { searchForm } = toRefs(data);
  const tableColumn = ref([
    {
      label: "员工姓名",
      prop: "staffName",
    },
    {
      label: "部门",
      prop: "deptName",
      width: 140,
    },
    {
      label: "月份",
      prop: "payDate",
    },
    {
      label: "基本工资",
      prop: "basicSalary",
    },
    {
      label: "计件工资",
      prop: "pieceworkSalary",
    },
    {
      label: "计时工资",
      prop: "hourlySalary",
    },
    {
      label: "其他收入",
      prop: "otherIncome",
    },
    {
      label: "社保个人",
      prop: "socialSecurityIndividuals",
    },
    {
      label: "公积金个人",
      prop: "providentFundIndividuals",
      width: 140,
    },
    {
      label: "工资个税",
      prop: "personalIncomeTax",
    },
    {
      label: "其他支出",
      prop: "otherDeductions",
    },
    {
      label: "应发工资",
      prop: "payableWages",
    },
    {
      label: "应扣工资",
      prop: "deductibleWages",
    },
    {
      label: "实发工资",
      prop: "actualWages",
    },
    {
      label: "备注",
      prop: "remark",
      width: 150,
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      width: 220,
      operation: [
        {
          name: "编辑",
          type: "text",
          clickFun: row => {
            openForm("edit", row);
          },
        },
        // {
        //   name: "查看",
        //   type: "text",
        //   clickFun: row => {
        //     openForm("view", row);
        //   },
        // },
      ],
    },
  ]);
  const tableData = ref([]);
  const selectedRows = ref([]);
  const tableLoading = ref(false);
  const page = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
  const formDia = ref();
  const dialogVisible = ref(false);
  const operationType = ref("add");
  const currentRow = ref({});
  const { proxy } = getCurrentInstance();
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
  const handleQuery = () => {
    page.current = 1;
    getList();
  };
  const pagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
  };
  const getList = () => {
    tableLoading.value = true;
    monthlyStatisticsListPage({ ...page, ...searchForm.value })
      .then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        page.total = res.data.total;
      })
      .catch(err => {
        tableLoading.value = false;
      });
  };
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
  };
  // æ‰“开弹框
  const openForm = (type, row) => {
    operationType.value = type;
    currentRow.value = row || {};
    dialogVisible.value = true;
    nextTick(() => {
      formDia.value?.openDialog(type, row);
    });
  };
  // åˆ é™¤
  const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
      ids = selectedRows.value.map(item => item.id);
    } else {
      proxy.$modal.msgWarning("请选择数据");
      return;
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        monthlyStatisticsDelete(ids).then(res => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
  // å¯¼å‡º
  const handleExport = () => {
    ElMessageBox.confirm("是否确认导出人员薪资台账?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        proxy.download(
          "/compensationPerformance/export",
          { ...searchForm.value, ...page },
          "人员薪资台账.xlsx"
        );
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
  onMounted(() => {
    getList();
  });
</script>
<style scoped>
  .search_form {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
    flex-wrap: wrap;
    gap: 10px;
  }
  .search_title {
    font-weight: 500;
    margin-right: 5px;
  }
  .ml10 {
    margin-left: 10px;
  }
  .table_list {
    margin-top: 20px;
  }
  .dialog-footer {
    text-align: right;
  }
</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: "确定",
          });
        }
      });
    });
  };
src/views/qualityManagement/finalInspection/components/formDia.vue
@@ -58,7 +58,7 @@
          </el-col>
          <el-col :span="12">
            <el-form-item label="数量:" prop="quantity">
              <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请输入" clearable :precision="2"/>
              <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请输入" clearable :precision="2" :disabled="quantityDisabled"/>
            </el-form-item>
          </el-col>
        </el-row>
@@ -123,7 +123,7 @@
</template>
<script setup>
import {ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
import {ref, reactive, toRefs, computed, getCurrentInstance, nextTick} from "vue";
import {getOptions} from "@/api/procurementManagement/procurementLedger.js";
import {modelList, productTreeList} from "@/api/basicData/product.js";
import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js";
@@ -164,6 +164,11 @@
  },
});
const { form, rules } = toRefs(data);
// ç¼–辑时:productMainId æˆ– purchaseLedgerId ä»»ä¸€æœ‰å€¼åˆ™æ•°é‡ç½®ç°
const quantityDisabled = computed(() => {
  const v = form.value || {};
  return !!(v.productMainId != null || v.purchaseLedgerId != null);
});
const supplierList = ref([]);
const productOptions = ref([]);
const tableColumn = ref([
src/views/qualityManagement/nonconformingManagement/components/formDia.vue
@@ -57,7 +57,9 @@
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="检验员:" prop="checkName">
              <el-input v-model="form.checkName" placeholder="请输入" clearable/>
              <el-select v-model="form.checkName" placeholder="请选择" clearable style="width: 100%">
                <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
@@ -91,7 +93,9 @@
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="处理人:" prop="dealName">
              <el-input v-model="form.dealName" placeholder="请输入" clearable/>
              <el-select v-model="form.dealName" placeholder="请选择" clearable style="width: 100%">
                <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
@@ -120,14 +124,17 @@
</template>
<script setup>
import {ref} from "vue";
import {ref, reactive, toRefs} from "vue";
import {modelList, productTreeList} from "@/api/basicData/product.js";
import {
  getQualityUnqualifiedInfo,
  qualityUnqualifiedAdd,
  qualityUnqualifiedUpdate
} from "@/api/qualityManagement/nonconformingManagement.js";
import {userListNoPage} from "@/api/system/user.js";
import useUserStore from "@/store/modules/user";
const { proxy } = getCurrentInstance()
const userStore = useUserStore()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
@@ -148,28 +155,56 @@
    inspectType: '',
    defectivePhenomena: '',
    dealResult: '',
    dealName: '',
    dealTime: '',
  },
  rules: {
    checkTime: [{ required: false, message: "请输入", trigger: "blur" },],
    process: [{ required: true, message: "请输入", trigger: "blur" }],
    checkName: [{ required: false, message: "请输入", trigger: "blur" }],
    checkName: [{ required: true, message: "请选择检验员", trigger: "change" }],
    productId: [{ required: true, message: "请输入", trigger: "blur" }],
    model: [{ required: true, message: "请输入", trigger: "blur" }],
    unit: [{ required: false, message: "请输入", trigger: "blur" }],
    quantity: [{ required: true, message: "请输入", trigger: "blur" }],
    checkCompany: [{ required: false, message: "请输入", trigger: "blur" }],
    checkResult: [{ required: false, message: "请输入", trigger: "blur" }],
    dealName: [{ required: true, message: "请选择处理人", trigger: "change" }],
  },
});
const { form, rules } = toRefs(data);
const productOptions = ref([]);
const modelOptions = ref([])
const modelOptions = ref([]);
const userList = ref([]); // æ£€éªŒå‘˜/处理人下拉列表
// æ‰“开弹框
const openDialog = (type, row) => {
const openDialog = async (type, row) => {
  operationType.value = type;
  try {
    const userRes = await userListNoPage();
    userList.value = userRes.data || [];
  } catch (e) {
    console.error("加载用户列表失败", e);
    userList.value = [];
  }
  dialogFormVisible.value = true;
  form.value = {}
  if (operationType.value === 'add') {
    form.value = {
      checkName: userStore.nickName || '',
      dealName: '',
      dealTime: '',
      dealResult: '',
      defectivePhenomena: '',
      inspectType: '',
      checkTime: '',
      productId: '',
      model: '',
      unit: '',
      quantity: '',
      productName: '',
    };
  } else {
    form.value = {};
  }
  getProductOptions();
  if (operationType.value === 'edit') {
    getQualityUnqualifiedInfo(row.id).then(res => {
src/views/qualityManagement/nonconformingManagement/components/inspectionFormDia.vue
@@ -90,7 +90,9 @@
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="处理人:" prop="dealName">
              <el-input v-model="form.dealName" placeholder="请输入" clearable/>
              <el-select v-model="form.dealName" placeholder="请选择" clearable style="width: 100%">
                <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
@@ -119,12 +121,13 @@
</template>
<script setup>
import {ref} from "vue";
import {ref, reactive, toRefs, computed} from "vue";
import {productTreeList} from "@/api/basicData/product.js";
import {
  getQualityUnqualifiedInfo,
  qualityUnqualifiedDeal
} from "@/api/qualityManagement/nonconformingManagement.js";
import {userListNoPage} from "@/api/system/user.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
@@ -162,12 +165,13 @@
    checkResult: [{ required: false, message: "请输入", trigger: "blur" }],
    defectivePhenomena: [{ required: true, message: "请输入", trigger: "blur" }],
    dealResult: [{ required: true, message: "请输入", trigger: "blur" }],
    dealName: [{ required: true, message: "请输入", trigger: "blur" }],
    dealName: [{ required: true, message: "请选择处理人", trigger: "change" }],
    dealTime: [{ required: true, message: "请输入", trigger: "change" }],
  },
});
const { form, rules } = toRefs(data);
const productOptions = ref([]);
const userList = ref([]); // å¤„理人下拉列表
const filteredRejectionHandling = computed(() => {
  const data = rejection_handling.value;
@@ -179,14 +183,23 @@
// æ‰“开弹框
const openDialog = (type, row) => {
const openDialog = async (type, row) => {
  operationType.value = type;
  // å¤„理人下拉列表
  try {
    const userRes = await userListNoPage();
    userList.value = userRes.data || [];
  } catch (e) {
    console.error("加载用户列表失败", e);
    userList.value = [];
  }
  dialogFormVisible.value = true;
  form.value = {}
  form.value = {};
  getProductOptions();
  if (operationType.value === 'edit') {
    getQualityUnqualifiedInfo(row.id).then(res => {
      const { inspectState, ...rest } = (res.data || {})
      // æœ‰æ•°æ®å°±æ˜¾ç¤ºé»˜è®¤å€¼ï¼Œæ²¡æœ‰å°±ä¸æ˜¾ç¤º
      form.value = { ...rest }
    })
  }
src/views/qualityManagement/nonconformingManagement/index.vue
@@ -60,7 +60,7 @@
<script setup>
import { Search } from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import {onMounted, ref, reactive, toRefs, nextTick, getCurrentInstance} from "vue";
import FormDia from "@/views/qualityManagement/nonconformingManagement/components/formDia.vue";
import {ElMessageBox} from "element-plus";
import {qualityUnqualifiedDel, qualityUnqualifiedListPage} from "@/api/qualityManagement/nonconformingManagement.js";
src/views/qualityManagement/processInspection/components/formDia.vue
@@ -10,7 +10,9 @@
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="工序:" prop="process">
              <el-input v-model="form.process" placeholder="请输入工序" clearable />
              <el-select v-model="form.process" placeholder="请选择工序" clearable :disabled="processQuantityDisabled" style="width: 100%">
                <el-option v-for="item in processList" :key="item.name" :label="item.name" :value="item.name"/>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
@@ -65,7 +67,7 @@
          </el-col>
          <el-col :span="12">
            <el-form-item label="数量:" prop="quantity">
              <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请输入" clearable :precision="2"/>
              <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请输入" clearable :precision="2" :disabled="processQuantityDisabled"/>
            </el-form-item>
          </el-col>
        </el-row>
@@ -130,15 +132,18 @@
</template>
<script setup>
import {ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
import {ref, reactive, toRefs, computed, getCurrentInstance, nextTick} from "vue";
import {getOptions} from "@/api/procurementManagement/procurementLedger.js";
import {modelList, productTreeList} from "@/api/basicData/product.js";
import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js";
import {qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId} from "@/api/qualityManagement/metricMaintenance.js";
import {userListNoPage} from "@/api/system/user.js";
import {qualityInspectParamInfo} from "@/api/qualityManagement/qualityInspectParam.js";
import { list } from "@/api/productionManagement/productionProcess";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
const operationType = ref('')
@@ -159,7 +164,7 @@
  },
  rules: {
    checkTime: [{ required: true, message: "请输入", trigger: "blur" },],
    process: [{ required: true, message: "请输入工序", trigger: "blur" }],
    process: [{ required: true, message: "请选择工序", trigger: "change" }],
    checkName: [{ required: false, message: "请输入", trigger: "blur" }],
    productId: [{ required: true, message: "请输入", trigger: "blur" }],
    productModelId: [{ required: true, message: "请选择", trigger: "change" }],
@@ -172,6 +177,12 @@
});
const userList = ref([]);
const { form, rules } = toRefs(data);
// ç¼–辑时:productMainId æˆ– purchaseLedgerId ä»»ä¸€æœ‰å€¼åˆ™å·¥åºã€æ•°é‡ç½®ç°
const processQuantityDisabled = computed(() => {
  const v = form.value || {};
  return !!(v.productMainId != null || v.purchaseLedgerId != null);
});
const processList = ref([]); // å·¥åºä¸‹æ‹‰åˆ—表(工序名称 name)
const supplierList = ref([]);
const productOptions = ref([]);
const tableColumn = ref([
@@ -210,6 +221,14 @@
    getOptions().then((res) => {
        supplierList.value = res.data;
    });
    // åŠ è½½å·¥åºä¸‹æ‹‰åˆ—è¡¨
    try {
        const res = await list();
        processList.value = res.data || [];
    } catch (e) {
        console.error("加载工序列表失败", e);
        processList.value = [];
    }
    let userLists = await userListNoPage();
    userList.value = userLists.data;
    // å…ˆé‡ç½®è¡¨å•数据(保持字段完整,避免弹窗首次渲染时触发必填红框“闪一下”)
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue
@@ -14,6 +14,7 @@
                  v-model="form.supplier"
                  placeholder="请选择"
                  clearable
                  :disabled="supplierQuantityDisabled"
              >
                <el-option
                    v-for="item in supplierList"
@@ -77,7 +78,7 @@
          <el-col :span="12">
            <el-form-item label="数量:" prop="quantity">
              <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请输入"
                               clearable :precision="2"/>
                               clearable :precision="2" :disabled="supplierQuantityDisabled"/>
            </el-form-item>
          </el-col>
        </el-row>
@@ -99,8 +100,9 @@
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="检验员:" prop="checkName">
              <el-input v-model="form.checkName" placeholder="请输入" clearable/>
              <el-select v-model="form.checkName" placeholder="请选择" clearable style="width: 100%">
                <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
@@ -143,12 +145,13 @@
</template>
<script setup>
import {ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
import {ref, reactive, toRefs, computed, getCurrentInstance, nextTick} from "vue";
import {getOptions} from "@/api/procurementManagement/procurementLedger.js";
import {modelList, productTreeList} from "@/api/basicData/product.js";
import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js";
import {qualityInspectParamDel, qualityInspectParamInfo} from "@/api/qualityManagement/qualityInspectParam.js";
import {qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId} from "@/api/qualityManagement/metricMaintenance.js";
import {userListNoPage} from "@/api/system/user.js";
const {proxy} = getCurrentInstance()
const emit = defineEmits(['close'])
@@ -216,6 +219,13 @@
const currentProductId = ref(0);
const testStandardOptions = ref([]); // æŒ‡æ ‡é€‰æ‹©ä¸‹æ‹‰æ¡†æ•°æ®
const modelOptions = ref([]);
const userList = ref([]); // æ£€éªŒå‘˜ä¸‹æ‹‰åˆ—表
// ç¼–辑时:productMainId æˆ– purchaseLedgerId ä»»ä¸€æœ‰å€¼åˆ™ä¾›åº”商、数量置灰
const supplierQuantityDisabled = computed(() => {
  const v = form.value || {};
  return !!(v.productMainId != null || v.purchaseLedgerId != null);
});
// æ‰“开弹框
const openDialog = async (type, row) => {
@@ -223,6 +233,14 @@
  getOptions().then((res) => {
    supplierList.value = res.data;
  });
  try {
    const userRes = await userListNoPage();
    userList.value = userRes.data || [];
  } catch (e) {
    console.error("加载检验员列表失败", e);
    userList.value = [];
  }
  // å…ˆé‡ç½®è¡¨å•数据(保持字段完整,避免弹窗首次渲染时触发必填红框“闪一下”)
    form.value = {
    checkTime: "",