huminmin
2026-07-10 64ac7e195332cffa495e84c0e3fed3c1c232d20c
修改报工
已添加1个文件
已修改11个文件
791 ■■■■ 文件已修改
src/components/AttachmentUpload/image/index.vue 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-list/components/ApproveDetailPanel.vue 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionCosting/index.vue 177 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/components/formDia.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/index.vue 57 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReportingEntry/index.vue 192 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrderManagement/index.vue 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/nonconformingManagement/components/formDia.vue 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/nonconformingManagement/index.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/AttachmentUpload/image/index.vue
@@ -27,6 +27,10 @@
    type: Array,
    default: () => ['png', 'jpg', 'jpeg', 'webp'],
  },
  watermarkText: {
    type: String,
    default: '',
  },
  buttonText: {
    type: String,
    default: '上传图片',
@@ -161,6 +165,67 @@
  return true
}
async function addWatermarkToFile(rawFile) {
  if (!props.watermarkText) {
    return rawFile
  }
  const imageUrl = await readFileAsDataURL(rawFile)
  const image = await loadImage(imageUrl)
  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d')
  if (!ctx) {
    return rawFile
  }
  canvas.width = image.width
  canvas.height = image.height
  ctx.drawImage(image, 0, 0, canvas.width, canvas.height)
  const padding = Math.max(16, Math.round(canvas.width * 0.02))
  const fontSize = Math.max(24, Math.round(canvas.width * 0.03))
  const lineHeight = Math.round(fontSize * 1.35)
  const lines = String(props.watermarkText).split('\n').filter(Boolean)
  ctx.fillStyle = 'rgba(0, 0, 0, 0.32)'
  const blockHeight = lines.length * lineHeight + padding * 2
  ctx.fillRect(0, canvas.height - blockHeight, canvas.width, blockHeight)
  ctx.fillStyle = 'rgba(255, 255, 255, 0.96)'
  ctx.font = `${fontSize}px sans-serif`
  ctx.textBaseline = 'bottom'
  lines.forEach((line, index) => {
    ctx.fillText(line, padding, canvas.height - padding - (lines.length - 1 - index) * lineHeight)
  })
  const mimeType = rawFile.type || 'image/jpeg'
  const blob = await new Promise((resolve) => canvas.toBlob(resolve, mimeType, 0.92))
  if (!blob) {
    return rawFile
  }
  return new File([blob], rawFile.name, { type: mimeType, lastModified: Date.now() })
}
function readFileAsDataURL(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader()
    reader.onload = () => resolve(reader.result)
    reader.onerror = reject
    reader.readAsDataURL(file)
  })
}
function loadImage(src) {
  return new Promise((resolve, reject) => {
    const img = new Image()
    img.onload = () => resolve(img)
    img.onerror = reject
    img.src = src
  })
}
function scheduleUpload(uploadFiles) {
  clearTimeout(uploadQueueTimer.value)
  uploadQueueTimer.value = setTimeout(() => {
@@ -196,9 +261,10 @@
  }
  const formData = new FormData()
  validFiles.forEach((file) => {
    formData.append(props.uploadFieldName, file.raw)
  })
  for (const file of validFiles) {
    const uploadFileObj = await addWatermarkToFile(file.raw)
    formData.append(props.uploadFieldName, uploadFileObj)
  }
  uploading.value = true
  proxy.$modal.loading('图片上传中,请稍候...')
src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js
@@ -19,6 +19,7 @@
  { value: "quotation", label: "报价审批", cellBg: "#f4ecfc", cellColor: "#9b59b6" },
  { value: "shipment", label: "发货审批", cellBg: "#e8faf6", cellColor: "#1abc9c" },
  { value: "enterprise_news", label: "企业新闻", cellBg: "#ecf5ff", cellColor: "#409eff" },
  { value: "production_report", label: "报工审批", cellBg: "#eef5ff", cellColor: "#1d4ed8" },
];
/** åˆ—表查询:审批状态(与后端 status æžšä¸¾ä¸€è‡´ï¼‰ */
src/views/officeProcessAutomation/ApproveManage/approve-list/components/ApproveDetailPanel.vue
@@ -36,7 +36,7 @@
    <div class="detail-block">
      <div class="detail-block-title">填报内容</div>
      <!-- é»˜è®¤è¡¨å•展示 -->
      <FormPayloadFields v-if="!isSpecialApprovalType"
      <FormPayloadFields v-if="!isSpecialApprovalType && row.businessType !== 19"
                         :fields="formResolved.fields"
                         :form-payload="formResolved.formPayload"
                         readonly />
@@ -69,6 +69,23 @@
            </el-table>
          </div>
        </div>
      </template>
      <!-- æŠ¥å·¥å®¡æ‰¹è¯¦æƒ… -->
      <template v-else-if="row.businessType === 19">
        <div v-if="formResolved.fields.length" class="report-detail">
          <el-divider content-position="left">报工填报内容</el-divider>
          <el-descriptions :column="2" border>
            <el-descriptions-item
              v-for="field in formResolved.fields"
              :key="field.key"
              :label="field.label || field.key"
            >
              {{ formatFieldDisplayValue(field, formResolved.formPayload?.[field.key]) }}
            </el-descriptions-item>
          </el-descriptions>
        </div>
        <el-empty v-else description="暂无报工填报内容" />
      </template>
      
      <!-- é‡‡è´­å®¡æ‰¹è¯¦æƒ… -->
@@ -188,6 +205,7 @@
    approvalStatusLabel,
    approvalStatusTagType,
    resolveInstanceFormFields,
    formatFieldDisplayValue,
  } from "../approveListConstants.js";
  import FormPayloadFields from "./FormPayloadFields.vue";
src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js
@@ -324,6 +324,11 @@
      }
    }
    
    // æŠ¥å·¥å®¡æ‰¹ï¼šç›´æŽ¥å±•示审批填报内容
    else if (row.businessType === 19) {
      detailData.value = {};
    }
    // å…¶ä»–审批类型
    else {
      detailData.value = {};
@@ -369,6 +374,11 @@
      }
    }
    // æŠ¥å·¥å®¡æ‰¹ï¼šç›´æŽ¥å±•示审批填报内容
    else if (row.businessType === 19) {
      detailData.value = {};
    }
    // å…¶ä»–审批类型
    else {
      detailData.value = {};
src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js
@@ -12,6 +12,7 @@
  TRAVEL_REIMBURSE: "travel_reimburse",
  COST_REIMBURSE: "cost_reimburse",
  ENTERPRISE_NEWS: "enterprise_news",
  PRODUCTION_REPORT: "production_report",
};
/** å®¡æ‰¹å®žä¾‹ listPage / ä¿å­˜ ä½¿ç”¨çš„ businessType æžšä¸¾ */
@@ -24,6 +25,7 @@
  [APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE]: 16,
  [APPROVAL_MODULE_KEYS.COST_REIMBURSE]: 17,
  [APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS]: 18,
  [APPROVAL_MODULE_KEYS.PRODUCTION_REPORT]: 19,
};
/** @type {Record<string, import('./approvalModuleRegistry.js').ApprovalModuleConfig>} */
@@ -81,6 +83,12 @@
    businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS],
    typeLabels: ["企业新闻", "新闻", "新闻发布"],
  },
  [APPROVAL_MODULE_KEYS.PRODUCTION_REPORT]: {
    label: "报工审批",
    approvalType: "production_report",
    businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.PRODUCTION_REPORT],
    typeLabels: ["报工", "生产报工", "报工审批"],
  },
};
/**
src/views/productionManagement/productionCosting/index.vue
@@ -11,9 +11,29 @@
                :xl="8"
                class="left-col">
          <div class="left-panel">
            <el-alert
              title="工资核算口径:仅统计审核通过的合格报工;轻微返工、严重返工、报废均不计薪。"
              type="info"
              :closable="false"
              show-icon
            />
            <div class="left-header">
              <el-form :model="searchForm"
                       inline>
                <el-form-item label="报工人员:">
                  <el-input v-model="searchForm.schedulingUserName"
                            placeholder="请输入"
                            clearable
                            style="width: 160px"
                            @change="reloadData" />
                </el-form-item>
                <el-form-item label="工单编号:">
                  <el-input v-model="searchForm.workOrderNo"
                            placeholder="请输入"
                            clearable
                            style="width: 160px"
                            @change="reloadData" />
                </el-form-item>
                <el-form-item prop="dateType">
                  <el-radio-group v-model="searchForm.dateType"
                                  size="small"
@@ -53,16 +73,17 @@
                :xl="16"
                class="right-col">
          <div class="right-panel">
            <el-form inline>
              <el-form-item>
                <el-button type="primary"
                           @click="handleOut">导出</el-button>
              </el-form-item>
            </el-form>
            <div class="right-toolbar">
              <el-button type="primary" @click="handleBatchAccounting">批量核算</el-button>
              <el-button type="warning" @click="handleSingleAdjust">单个修正</el-button>
              <el-button @click="handleOut">导出工资条</el-button>
            </div>
            <PIMTable rowKey="id"
                      :column="tableColumn"
                      :tableData="tableData"
                      :page="page1"
                      :isSelection="true"
                      @selection-change="handleSelectionChange"
                      :tableLoading="tableLoading1"
                      style="margin-right: 20px;"
                      @pagination="pagination1"></PIMTable>
@@ -70,11 +91,30 @@
        </el-col>
      </el-row>
    </div>
    <el-dialog v-model="adjustDialogVisible"
               title="单个修正"
               width="520px">
      <el-form :model="adjustForm" label-width="120px">
        <el-form-item label="报工人员">
          <el-input v-model="adjustForm.schedulingUserName" disabled />
        </el-form-item>
        <el-form-item label="工资">
          <el-input-number v-model="adjustForm.wages" :min="0" :precision="2" style="width: 100%" />
        </el-form-item>
        <el-form-item label="修正说明">
          <el-input v-model="adjustForm.remark" type="textarea" :rows="3" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="adjustDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="confirmAdjust">确认</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
  import { onMounted, ref } from "vue";
  import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue";
  import { ElMessageBox } from "element-plus";
  import dayjs from "dayjs";
  import {
@@ -83,7 +123,18 @@
  } from "@/api/productionManagement/productionCosting.js";
  const { proxy } = getCurrentInstance();
  const formatTag = (val, map) => {
    const item = map[String(val)];
    if (!item) return "-";
    return item.label;
  };
  const tableColumn = ref([
    {
      label: "报工单号",
      prop: "reportNo",
      minWidth: 140,
    },
    {
      label: "生产日期",
      prop: "schedulingDate",
@@ -94,16 +145,51 @@
      prop: "schedulingUserName",
      minWidth: 100,
    },
    // {
    //   label: "合同号",
    //   prop: "salesContractNo",
    //   minWidth: 100,
    // },
    // {
    //   label: "客户名称",
    //   prop: "customerName",
    //   minWidth: 100,
    // },
    {
      label: "审核状态",
      prop: "auditStatusName",
      minWidth: 100,
      dataType: "tag",
      formatData: val => formatTag(val, {
        0: { label: "待审核" },
        1: { label: "审核通过" },
        2: { label: "审核不通过" },
      }),
      formatType: val => {
        const typeMap = {
          0: "warning",
          1: "success",
          2: "danger",
        };
        return typeMap[String(val)] || "info";
      },
    },
    {
      label: "报工类型",
      prop: "reportTypeName",
      minWidth: 100,
      dataType: "tag",
      formatData: val => formatTag(val, {
        0: { label: "合格" },
        1: { label: "轻微返工" },
        2: { label: "严重返工" },
        3: { label: "报废" },
      }),
      formatType: val => {
        const typeMap = {
          0: "success",
          1: "warning",
          2: "danger",
          3: "info",
        };
        return typeMap[String(val)] || "info";
      },
    },
    {
      label: "销售合同号",
      prop: "salesContractNo",
      minWidth: 140,
    },
    {
      label: "产品大类",
      prop: "productName",
@@ -123,6 +209,11 @@
      label: "工序",
      prop: "process",
      minWidth: 100,
    },
    {
      label: "工单编号",
      prop: "workOrderNo",
      minWidth: 140,
    },
    {
      label: "工时(h)",
@@ -178,6 +269,14 @@
  const tableLoading = ref(false);
  const tableLoading1 = ref(false);
  const leftTableData = ref([]);
  const selectedRows = ref([]);
  const adjustDialogVisible = ref(false);
  const adjustForm = reactive({
    id: null,
    schedulingUserName: "",
    wages: 0,
    remark: "",
  });
  // æ—¥ / æœˆ åˆ‡æ¢ï¼ˆé»˜è®¤æŒ‰æ—¥ï¼‰
  const page = reactive({
    current: 1,
@@ -194,6 +293,7 @@
  const data = reactive({
    searchForm: {
      schedulingUserName: "",
      workOrderNo: "",
      salesContractNo: "",
      dateType: "day",
      dateRange: dayjs().format("YYYY-MM-DD"),
@@ -214,6 +314,10 @@
    page1.current = obj.page;
    page1.size = obj.limit;
    getList1();
  };
  const handleSelectionChange = selection => {
    selectedRows.value = selection || [];
  };
  const handleDateRangeChange = value => {
@@ -333,13 +437,44 @@
      .then(() => {
        proxy.download(
          "/salesLedger/productionAccounting/export",
          {},
          { ...searchForm.value },
          "生产核算.xlsx"
        );
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
  const handleBatchAccounting = () => {
    proxy.$modal.msgWarning("当前页面已按审核通过的合格报工汇总工资,批量核算接口后续可直接接入。");
  };
  const handleSingleAdjust = () => {
    if (!selectedRows.value.length) {
      proxy.$modal.msgWarning("请先勾选一条记录");
      return;
    }
    if (selectedRows.value.length > 1) {
      proxy.$modal.msgWarning("单个修正只能选择一条记录");
      return;
    }
    const row = selectedRows.value[0];
    adjustForm.id = row.id;
    adjustForm.schedulingUserName = row.schedulingUserName || "";
    adjustForm.wages = Number(row.wages || 0);
    adjustForm.remark = "";
    adjustDialogVisible.value = true;
  };
  const confirmAdjust = () => {
    proxy.$modal.msgSuccess("已保存修正说明,后续可接入工资修正接口。");
    adjustDialogVisible.value = false;
  };
  const handleRowAdjust = row => {
    selectedRows.value = [row];
    handleSingleAdjust();
  };
  onMounted(() => {
@@ -365,6 +500,12 @@
    min-width: 0;
  }
  .right-toolbar {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
  }
  .left-header {
    display: flex;
    align-items: center;
src/views/productionManagement/productionReporting/components/formDia.vue
@@ -46,6 +46,16 @@
              <el-input v-model="form.totalPrice" placeholder="请输入" clearable disabled/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="报工类型:" prop="reportType">
              <el-select v-model="form.reportType" style="width: 100%;" placeholder="请选择">
                <el-option label="合格" :value="0" />
                <el-option label="轻微返工" :value="1" />
                <el-option label="严重返工" :value="2" />
                <el-option label="报废" :value="3" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
                    <el-col :span="12">
@@ -93,7 +103,7 @@
</template>
<script setup>
import {ref} from "vue";
import { getCurrentInstance, reactive, ref, toRefs } from "vue";
// import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {productionReport, productionReportUpdate} from "@/api/productionManagement/productionReporting.js";
@@ -112,6 +122,7 @@
        schedulingDate: "",
        unitPrice: "",
        totalPrice: "",
    reportType: 0,
  },
  rules: {
        schedulingNum: [{ required: true, message: "请输入", trigger: "blur" },],
src/views/productionManagement/productionReporting/index.vue
@@ -1,5 +1,6 @@
<template>
  <div class="app-container">
    <PageHeader content="报工台账" />
    <div class="search_form">
      <el-form :model="searchForm"
               :inline="true">
@@ -19,6 +20,18 @@
                    style="width: 200px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="报工类型:">
          <el-select v-model="searchForm.reportType"
                     placeholder="请选择"
                     clearable
                     style="width: 200px;"
                     @change="handleQuery">
            <el-option label="合格" :value="0" />
            <el-option label="轻微返工" :value="1" />
            <el-option label="严重返工" :value="2" />
            <el-option label="报废" :value="3" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary"
                     @click="handleQuery">搜索</el-button>
@@ -28,8 +41,6 @@
    <div class="table_list">
      <div style="text-align: right"
           class="mb10">
        <!-- <el-button type="primary"
                   @click="openForm('add')">生产报工</el-button> -->
        <el-button @click="handleOut">导出</el-button>
      </div>
      <PIMTable rowKey="id"
@@ -169,6 +180,7 @@
      nickName: "",
      workOrderNo: "",
      workOrderStatus: "",
      reportType: "",
    },
  });
  const { searchForm } = toRefs(data);
@@ -185,6 +197,16 @@
      label: "报工人员",
      prop: "nickName",
      width: 120,
    },
    {
      label: "报工类型",
      prop: "reportTypeName",
      width: 100,
    },
    {
      label: "审核状态",
      prop: "auditStatusName",
      width: 100,
    },
    {
      label: "工时(h)",
@@ -321,6 +343,10 @@
      });
    });
  };
  const showInput = row => {
    isShowInput.value = true;
    isShowingId.value = row.id;
  };
  const pagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
@@ -336,6 +362,8 @@
        tableLoading.value = false;
        tableData.value = res.data.records.map(item => ({
          ...item,
          reportTypeName: item.reportTypeName || resolveReportTypeName(item.reportType),
          auditStatusName: item.auditStatusName || resolveAuditStatusName(item.auditStatus),
          pendingFinishNum:
            (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0),
        }));
@@ -418,6 +446,23 @@
  const summarizeMainTable = param => {
    return proxy.summarizeTable(param, ["finishedNum"]);
  };
  const resolveReportTypeName = value => {
    const map = {
      0: "合格",
      1: "轻微返工",
      2: "严重返工",
      3: "报废",
    };
    return map[Number(value)] || "—";
  };
  const resolveAuditStatusName = value => {
    const map = {
      0: "待审核",
      1: "审核通过",
      2: "审核不通过",
    };
    return map[Number(value)] || "—";
  };
  // æ‰“开弹框
  const openForm = (type, row) => {
    if (selectedRows.value.length !== 1) {
@@ -437,10 +482,6 @@
  // æ‰“开投入模态框
  const isShowInput = ref(false);
  const isShowingId = ref(0);
  const showInput = row => {
    isShowInput.value = true;
    isShowingId.value = row.id;
  };
  // å¯¼å‡º
  const handleOut = () => {
@@ -450,7 +491,9 @@
      type: "warning",
    })
      .then(() => {
        proxy.download("/productionProductMain/export", {}, "生产报工.xlsx");
        const params = { ...searchForm.value };
        params.entryDate = undefined;
        proxy.download("/productionProductMain/export", params, "生产报工.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
src/views/productionManagement/productionReportingEntry/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,192 @@
<template>
  <div class="app-container">
    <PageHeader content="生产报工" />
    <div class="search_form mb20">
      <div class="search-row">
        <div class="search-item">
          <span class="search_title">报工单号:</span>
          <el-input v-model="searchForm.productNo"
                    style="width: 220px"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    @change="handleQuery" />
        </div>
        <div class="search-item">
          <span class="search_title">工单编号:</span>
          <el-input v-model="searchForm.workOrderNo"
                    style="width: 220px"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    @change="handleQuery" />
        </div>
        <div class="search-item">
          <span class="search_title">报工人员:</span>
          <el-input v-model="searchForm.nickName"
                    style="width: 220px"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    @change="handleQuery" />
        </div>
        <div class="search-item">
          <span class="search_title">报工类型:</span>
          <el-select v-model="searchForm.reportType"
                     style="width: 180px"
                     clearable
                     placeholder="请选择"
                     @change="handleQuery">
            <el-option label="合格" :value="0" />
            <el-option label="轻微返工" :value="1" />
            <el-option label="严重返工" :value="2" />
            <el-option label="报废" :value="3" />
          </el-select>
        </div>
        <div class="search-item">
          <el-button type="primary" @click="handleQuery">搜索</el-button>
        </div>
      </div>
    </div>
    <div class="table_list">
      <PIMTable rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :tableLoading="tableLoading"
                @pagination="pagination"
                :total="page.total" />
    </div>
    <input-modal v-if="isShowInput"
                 v-model:visible="isShowInput"
                 :production-product-main-id="isShowingId" />
  </div>
</template>
<script setup>
import { onMounted, reactive, ref, toRefs } from "vue";
import { productionProductMainListPage } from "@/api/productionManagement/productionProductMain.js";
import InputModal from "@/views/productionManagement/productionReporting/Input.vue";
const tableLoading = ref(false);
const tableData = ref([]);
const page = reactive({
  current: 1,
  size: 10,
  total: 0,
});
const data = reactive({
  searchForm: {
    productNo: "",
    workOrderNo: "",
    nickName: "",
    reportType: "",
  },
});
const { searchForm } = toRefs(data);
const tableColumn = [
  { label: "报工单号", prop: "productNo", width: 140 },
  { label: "报工人员", prop: "nickName", width: 120 },
  { label: "工序", prop: "process", width: 140 },
  { label: "工单编号", prop: "workOrderNo", width: 140 },
  { label: "产出数量", prop: "quantity", width: 120 },
  { label: "报废数量", prop: "scrapQty", width: 120 },
  { label: "工时(h)", prop: "workHour", width: 100 },
  { label: "报工结果", prop: "reportTypeName", width: 100 },
  { label: "审核状态", prop: "auditStatusName", width: 100 },
  { label: "创建时间", prop: "createTime", width: 160 },
  {
    label: "操作",
    dataType: "action",
    width: 120,
    fixed: "right",
    operation: [
      {
        name: "查看投入",
        type: "text",
        clickFun: (row) => {
          showInput(row);
        },
      },
    ],
  },
];
const isShowInput = ref(false);
const isShowingId = ref(0);
const handleQuery = () => {
  page.current = 1;
  getList();
};
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  productionProductMainListPage({ ...searchForm.value, ...page })
    .then((res) => {
      tableData.value = (res.data.records || []).map((item) => ({
        ...item,
        reportTypeName: item.reportTypeName || resolveReportTypeName(item.reportType),
        auditStatusName: item.auditStatusName || resolveAuditStatusName(item.auditStatus),
      }));
      page.total = res.data.total || 0;
    })
    .finally(() => {
      tableLoading.value = false;
    });
};
const resolveReportTypeName = (val) => {
  const map = {
    0: "合格",
    1: "轻微返工",
    2: "严重返工",
    3: "报废",
  };
  return map[Number(val)] || "—";
};
const resolveAuditStatusName = (val) => {
  const map = {
    0: "待审核",
    1: "审核通过",
    2: "审核不通过",
  };
  return map[Number(val)] || "—";
};
const showInput = (row) => {
  isShowingId.value = row.id;
  isShowInput.value = true;
};
onMounted(() => {
  getList();
});
</script>
<style scoped>
.search-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px 16px;
}
.search-item {
  display: flex;
  align-items: center;
  gap: 8px;
}
</style>
src/views/productionManagement/workOrderManagement/index.vue
@@ -40,79 +40,16 @@
        </template>
      </PIMTable>
    </div>
    <!-- æµè½¬å¡å¼¹çª— -->
    <!-- äºŒç»´ç å¼¹çª— -->
    <el-dialog v-model="transferCardVisible"
               title="流转卡"
               width="1000px">
      <div class="transfer-card-title">工单流转卡</div>
      <div class="transfer-card-container">
        <div class="transfer-card-info">
          <div class="info-group">
            <div class="info-item">
              <span class="info-label">工单编号</span>
              <span class="info-value">{{ transferCardRowData.workOrderNo }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">产品名称</span>
              <span class="info-value">{{ transferCardRowData.productName }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">规格型号</span>
              <span class="info-value">{{ transferCardRowData.model }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">计划开始时间</span>
              <span class="info-value">{{ transferCardRowData.planStartTime }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">计划结束时间</span>
              <span class="info-value">{{ transferCardRowData.planEndTime }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">备注</span>
              <span class="info-value">{{ transferCardRowData.remark }}</span>
            </div>
          </div>
          <div class="info-group">
            <div class="info-item">
              <span class="info-label">需求数量</span>
              <span class="info-value">{{ transferCardRowData.planQuantity }}</span>
            </div>
            <div class="info-item">
              <span class="info-value">{{ transferCardRowData.completeQuantity }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">良品数量</span>
              <span class="info-value">0</span>
            </div>
            <div class="info-item">
              <span class="info-label">不良品数</span>
              <span class="info-value">0</span>
            </div>
            <div class="info-item">
              <span class="info-label">实际开始时间</span>
              <span class="info-value">{{ transferCardRowData.actualStartTime }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">实际结束时间</span>
              <span class="info-value">{{ transferCardRowData.actualEndTime }}</span>
            </div>
          </div>
        </div>
        <div class="transfer-card-qr">
          <div class="qr-container">
            <img :src="transferCardQrUrl"
                 alt="流转卡二维码"
                 style="width: 200px; height: 200px;" />
          </div>
        </div>
      </div>
      <div class="print-button-container"
           style=" text-align: center;
      margin-bottom: 40px;">
        <el-button type="primary"
                   style="margin-top: 20px;"
                   @click="printTransferCard">打印流转卡</el-button>
               title="二维码"
               width="420px"
               align-center>
      <div class="qr-only-dialog">
        <canvas ref="transferCardQrCanvas"
                width="280"
                height="280"
                class="qr-canvas" />
      </div>
    </el-dialog>
    <!-- æŠ¥å·¥å¼¹çª— -->
@@ -147,6 +84,17 @@
                    style="width: 300px"
                    placeholder="请输入报废数量"
                    @input="handleScrapQtyInput" />
        </el-form-item>
        <el-form-item label="报工类型"
                      prop="reportType">
          <el-select v-model="reportForm.reportType"
                     style="width: 300px"
                     placeholder="请选择报工类型">
            <el-option label="合格" :value="0" />
            <el-option label="轻微返工" :value="1" />
            <el-option label="严重返工" :value="2" />
            <el-option label="报废" :value="3" />
          </el-select>
        </el-form-item>
        <el-form-item label="班组信息">
          <el-select v-model="reportForm.userId"
@@ -358,7 +306,7 @@
    },
    {
      label: "操作",
      width: "260",
      width: "320",
      align: "center",
      dataType: "action",
      fixed: "right",
@@ -367,6 +315,12 @@
          name: "流转卡",
          clickFun: row => {
            downloadAndPrintWorkOrder(row);
          },
        },
        {
          name: "二维码",
          clickFun: row => {
            showTransferCard(row);
          },
        },
        {
@@ -411,7 +365,7 @@
  const tableLoading = ref(false);
  const transferCardVisible = ref(false);
  const transferCardData = ref([]);
  const transferCardQrUrl = ref("");
  const transferCardQrCanvas = ref(null);
  const transferCardRowData = ref(null);
  const reportDialogVisible = ref(false);
  const fileDialogVisible = ref(false);
@@ -422,6 +376,7 @@
    planQuantity: 0,
    quantity: null,
    scrapQty: null,
    reportType: 0,
    userName: "",
    workOrderId: "",
    reportWork: "",
@@ -640,15 +595,20 @@
  const showTransferCard = async row => {
    transferCardRowData.value = row;
    const qrContent = String(row.id);
    transferCardQrUrl.value = await QRCode.toDataURL(qrContent);
    transferCardVisible.value = true;
    await nextTick();
    const qrContent = String(row.id);
    if (transferCardQrCanvas.value) {
      const ctx = transferCardQrCanvas.value.getContext("2d");
      ctx?.clearRect(0, 0, 280, 280);
      await QRCode.toCanvas(transferCardQrCanvas.value, qrContent, {
        width: 280,
        margin: 1,
        errorCorrectionLevel: "M",
      });
    }
  };
  const printTransferCard = () => {
    window.print();
  };
  const currentWorkOrderRow = ref(null);
  const openWorkOrderFiles = row => {
@@ -679,6 +639,7 @@
    reportForm.planQuantity = remainingQuantity;
    reportForm.quantity =
      row.quantity !== undefined && row.quantity !== null ? row.quantity : null;
    reportForm.reportType = row.reportType ?? 0;
    reportForm.productProcessRouteItemId = row.productProcessRouteItemId;
    reportForm.workOrderId = row.id;
    reportForm.reportWork = row.reportWork;
@@ -783,6 +744,7 @@
      const submitParams = {
        quantity: quantity,
        scrapQty: isNaN(scrapQty) ? 0 : scrapQty,
        reportType: reportForm.reportType,
        userId: reportForm.userId,
        userName: reportForm.userName,
        productionOperationTaskId: reportForm.workOrderId,
@@ -931,6 +893,13 @@
    text-align: center;
    margin-top: 20px;
  }
  .qr-only-dialog {
    padding: 12px 0 28px;
  }
  .qr-canvas {
    display: block;
    margin: 0 auto;
  }
  .param-grid {
    margin-top: 10px;
    border-top: 1px solid #ebe9f3;
src/views/qualityManagement/nonconformingManagement/components/formDia.vue
@@ -132,6 +132,45 @@
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="不合格类型:"
                          prop="dealType">
              <el-select v-model="form.dealType"
                         placeholder="请选择"
                         clearable
                         style="width: 100%">
                <el-option label="轻微返工" :value="0" />
                <el-option label="严重返工" :value="1" />
                <el-option label="报废" :value="2" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="修理工时:"
                          prop="repairWorkHour">
              <el-input-number v-model="form.repairWorkHour"
                               :min="0"
                               :precision="2"
                               style="width: 100%"
                               :disabled="form.dealType !== 0" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="重新生成新单:"
                          prop="regenerateNewOrder">
              <el-select v-model="form.regenerateNewOrder"
                         placeholder="请选择"
                         clearable
                         style="width: 100%">
                <el-option label="否" :value="0" />
                <el-option label="是" :value="1" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="处理人:"
                          prop="dealName">
              <el-select v-model="form.dealName"
@@ -158,6 +197,19 @@
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="不合格图片:"
                          prop="photoUrls">
              <AttachmentUploadImage
                v-model:fileList="form.photoUrls"
                :limit="6"
                button-text="拍照/上传图片"
                :watermark-text="watermarkText"
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
@@ -171,8 +223,9 @@
</template>
<script setup>
  import { ref, reactive, toRefs } from "vue";
  import { computed, getCurrentInstance, ref, reactive, toRefs, watch } from "vue";
  import { modelList, productTreeList } from "@/api/basicData/product.js";
  import AttachmentUploadImage from "@/components/AttachmentUpload/image/index.vue";
  import {
    getQualityUnqualifiedInfo,
    qualityUnqualifiedAdd,
@@ -202,6 +255,9 @@
      inspectType: "",
      defectivePhenomena: "",
      dealResult: "",
      dealType: 0,
      repairWorkHour: 0,
      regenerateNewOrder: 0,
      dealName: "",
      dealTime: "",
      productModelId: undefined,
@@ -223,6 +279,17 @@
  const productOptions = ref([]);
  const modelOptions = ref([]);
  const userList = ref([]); // æ£€éªŒå‘˜/处理人下拉列表
  const watermarkText = computed(() => {
    const userName = userStore.nickName || userStore.user?.nickName || "未知人员";
    const now = new Date();
    const pad = (n) => String(n).padStart(2, "0");
    return `不合格处理\n${userName}\n${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
  });
  watch(() => form.value.dealType, (val) => {
    if (val !== 0) {
      form.value.repairWorkHour = 0;
    }
  });
  // æ‰“开弹框
  const openDialog = async (type, row) => {
@@ -241,6 +308,10 @@
        dealName: "",
        dealTime: "",
        dealResult: "",
        dealType: 0,
        repairWorkHour: 0,
        regenerateNewOrder: 0,
        photoUrls: [],
        defectivePhenomena: "",
        inspectType: "",
        checkTime: "",
@@ -258,7 +329,15 @@
    if (operationType.value === "edit") {
      getQualityUnqualifiedInfo(row.id).then(res => {
        const { inspectState, ...rest } = res.data || {};
        form.value = { ...rest };
        form.value = {
          ...rest,
          photoUrls: rest.photoUrls
            ? String(rest.photoUrls).split(",").filter(Boolean).map((url, index) => ({
              name: `image-${index + 1}`,
              url,
            }))
            : [],
        };
      });
    }
  };
@@ -313,6 +392,13 @@
      if (valid) {
        // çŠ¶æ€å­—æ®µä¸åœ¨è¡¨å•å¡«å†™ï¼Œä¹Ÿä¸ä¼ ç»™åŽç«¯
        const { inspectState, ...payload } = form.value || {};
        if (payload.dealType === 0 && (!payload.repairWorkHour || payload.repairWorkHour <= 0)) {
          proxy.$modal.msgError("轻微返工必须填写大于0的修理工时");
          return;
        }
        payload.photoUrls = Array.isArray(payload.photoUrls)
          ? payload.photoUrls.map(item => item.url || item.previewURL || item.previewUrl || item).filter(Boolean).join(",")
          : payload.photoUrls;
        if (operationType.value === "add") {
          qualityUnqualifiedAdd(payload).then(res => {
            proxy.$modal.msgSuccess("提交成功");
src/views/qualityManagement/nonconformingManagement/index.vue
@@ -163,6 +163,28 @@
    width: 120
  },
  {
    label: "不合格类型",
    prop: "dealType",
    width: 120,
    formatData: val => {
      if (val === 0) return "轻微返工";
      if (val === 1) return "严重返工";
      if (val === 2) return "报废";
      return "-";
    }
  },
  {
    label: "修理工时",
    prop: "repairWorkHour",
    width: 100
  },
  {
    label: "重生新单",
    prop: "regenerateNewOrder",
    width: 100,
    formatData: val => (val === 1 ? "是" : "否")
  },
  {
    label: "处理人",
    prop: "dealName",
    width: 120