Browse Source

批记录审批页面修改

lucw 10 months ago
parent
commit
1fae41d7ff

+ 94 - 0
src/api/producetaskrecordrulesrecord/index.js

@@ -42,3 +42,97 @@ export async function getById(id) {
   }
   return Promise.reject(new Error(res.data.message));
 }
+
+// 根据工单id获取工序列表
+export async function getTaskInstanceList(id) {
+  const res = await request.get(`/pda/mes/workorder/getTaskInstanceById/${id}`);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 查询班组
+export async function getTeam(id) {
+  const res = await request.get(`/main/team/getById/${id}`);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 工单详情
+export async function getWorkOrderById(id) {
+  const res = await request.get(`/mes/workorder/getById/${id}`);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// /main/categoryparam/getMaterialQuotaInfo/{bomCategoryId}/{produceTaskId}
+export async function getMaterialQuotaInfo(bomCategoryId, produceTaskId) {
+  const res = await request.get(
+    `/main/categoryparam/getMaterialQuotaInfo/${bomCategoryId}/${produceTaskId}`
+  );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// /main/category/getCategoryAndLevelByCategoryId/{categoryId}
+export async function getCategoryAndLevelByCategoryId(categoryId) {
+  const res = await request.get(
+    `/main/category/getCategoryAndLevelByCategoryId/${categoryId}`
+  );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// /mes/producetaskrulerecordpickdetail/queryListByWorkOrderId post
+export async function queryListByWorkOrderId(body) {
+  const res = await request.post(
+    '/mes/producetaskrulerecordpickdetail/queryListByWorkOrderId',
+    body
+  );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+//  /mes/producetaskrulerecordpickdetail/getPickAndFeed 参数:workOrderId produceTaskId或produceTaskInstanceId
+export async function getPickAndFeed(body) {
+  const res = await request.post(
+    '/mes/producetaskrulerecordpickdetail/getPickAndFeed',
+    body
+  );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// /mes/workorder/inStoreageRecord
+export async function inStoreageRecord(body) {
+  const res = await request.post('/mes/workorder/inStoreageRecord', body);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// /mes/workorder/listProduceTaskAndFormNum
+export async function listProduceTaskAndFormNum(body) {
+  const res = await request.post(
+    '/mes/workorder/listProduceTaskAndFormNum',
+    body
+  );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}

+ 5 - 3
src/enum/dict.js

@@ -92,7 +92,10 @@ export default {
   物品机型: 'product_model_key',
   物品颜色: 'product_color_key',
   检查有效期单位: 'validity_unit',
-  记录规则类型: 'record_sheet'
+  记录规则类型: 'record_sheet',
+  记录规则报工类型: 'record_rules_report_work_type',
+  记录规则执行方式: 'produce_task_config_execute_method',
+  记录规则事项类型: 'record_rules_item_type'
 };
 
 export const numberList = [
@@ -317,7 +320,6 @@ export const topLevel2 = [
   }
 ];
 
-
 //采购订单整体进度
 export const purchaseOrderProgressStatusEnum = [
   { value: 0, label: '未开始' },
@@ -327,4 +329,4 @@ export const purchaseOrderProgressStatusEnum = [
   { value: 103, label: '质检中' },
   { value: 200, label: '已入库' },
   { value: 1000, label: '完成' }
-];
+];

+ 784 - 83
src/views/bpm/handleTask/components/mesBatchRecordManage/detailDialog.vue

@@ -7,130 +7,505 @@
       :rules="rules"
       label-width="130px"
       v-loading="loading"
+      disabled
     >
-      <header-title title="产品信息"></header-title>
+      <header-title title="事项信息"></header-title>
+
       <el-row style="margin-bottom: 20px">
         <el-col :span="8">
-          <el-form-item label="生产订单">
-            <el-input :value="details.workOrderCode" disabled></el-input>
+          <el-form-item label="记录编码">
+            <el-input
+              v-model="details.code"
+              placeholder="系统自动生成"
+              disabled
+            ></el-input>
           </el-form-item>
         </el-col>
         <el-col :span="8">
-          <el-form-item label="工序名称">
-            <el-input :value="details.produceTaskName" disabled></el-input>
+          <el-form-item label="类型" required prop="itemType">
+            <DictSelection
+              dictName="记录规则事项类型"
+              v-model="details.itemType"
+              disabled
+            >
+            </DictSelection>
           </el-form-item>
         </el-col>
-        <el-col :span="8"> </el-col>
         <el-col :span="8">
-          <el-form-item label="产品编码">
-            <el-input :value="details.productCode" disabled></el-input>
+          <el-form-item label="配置执行方式" required prop="executeMethod">
+            <DictSelection
+              dictName="记录规则执行方式"
+              v-model="details.executeMethod"
+            >
+            </DictSelection>
           </el-form-item>
         </el-col>
+      </el-row>
+      <el-row>
         <el-col :span="8">
-          <el-form-item label="产品名称">
-            <el-input :value="details.productName" disabled></el-input>
+          <el-form-item
+            v-if="details.itemType == '1'"
+            label="选择设备"
+            required
+            prop="deviceName"
+          >
+            <div class="mask-box">
+              <el-input
+                v-model="details.deviceName"
+                placeholder="请选择设备"
+                size="small"
+                :readonly="true"
+              >
+                <template #append>
+                  <el-button size="small">选择设备</el-button>
+                </template>
+              </el-input>
+            </div>
           </el-form-item>
         </el-col>
-        <el-col :span="8">
-          <el-form-item label="批次号">
+
+        <el-col v-if="details.itemType == '3'" :span="8">
+          <el-form-item label="关联任务">
             <el-input
-              :value="details.batchNo"
-              disabled
-            ></el-input> </el-form-item
-        ></el-col>
-        <el-col :span="8">
-          <el-form-item label="规格">
-            <el-input :value="details.specification" disabled></el-input>
+              v-model="details.itemTaskName"
+              placeholder="请输入任务名称"
+            ></el-input>
           </el-form-item>
         </el-col>
-        <el-col :span="8">
-          <el-form-item label="型号">
-            <el-input :value="details.productModel" disabled></el-input>
+        <el-col v-if="details.itemType == '2'" :span="8">
+          <el-form-item label="关联记录规则">
+            <div class="mask-box">
+              <el-input
+                v-model="details.ruleName"
+                placeholder="请选择记录规则名称"
+                size="small"
+                readonly
+              >
+                <template #append>
+                  <el-button size="small">选择规则</el-button>
+                </template>
+              </el-input>
+            </div>
           </el-form-item>
         </el-col>
-        <el-col :span="8">
-          <el-form-item label="要求生产数量">
-            <el-input :value="details.formingNum" disabled></el-input>
+        <el-col v-if="details.itemType == '2'" :span="8">
+          <el-form-item label="记录规则分类">
+            <DictSelection
+              dictName="记录规则类型"
+              v-model="details.recordRulesClassify"
+              disabled
+            >
+            </DictSelection>
           </el-form-item>
         </el-col>
+        <el-col v-if="details.itemType == '1'" :span="8">
+          <el-form-item label="关联事项规则" required prop="ruleName">
+            <div class="mask-box">
+              <el-input
+                v-model="details.ruleName"
+                placeholder="请选择设备有关计划规则,如保养规则,巡点检规则等"
+                size="small"
+                readonly
+              >
+                <template #append>
+                  <el-button size="small">选择规则</el-button>
+                </template>
+              </el-input>
+            </div>
+          </el-form-item></el-col
+        >
       </el-row>
-      <header-title title="基本信息"></header-title>
-      <!-- 记录规则 -->
-      <el-row v-if="details.executeMethod == 2" style="margin-bottom: 20px">
+
+      <header-title
+        title="基本信息"
+        v-if="details.itemType == 2 && details.reportWorkType != 4"
+        style="margin-top: 20px"
+      ></header-title>
+
+      <template v-if="details.itemType == 2 && details.reportWorkType != 4">
+        <el-row style="margin-bottom: 20px">
+          <el-col :span="8">
+            <el-form-item
+              v-if="details.recordRulesClassify == 3"
+              label="关联设备"
+            >
+              <el-input
+                v-model="details.deviceName"
+                placeholder="自动带出"
+                disabled
+              ></el-input>
+            </el-form-item>
+            <el-form-item v-else label="车间区域">
+              <el-select
+                v-model="details.workshopAreaId"
+                placeholder="请选择车间区域"
+                filterable
+                clearable
+                style="width: 100%"
+                disabled
+              >
+                <el-option
+                  v-for="item in workshopAreaList"
+                  :key="item.id"
+                  :label="item.name"
+                  :value="item.id"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="检查开始时间" required prop="checkStartTime">
+              <el-date-picker
+                v-model="details.checkStartTime"
+                type="datetime"
+                format="yyyy-MM-dd HH:mm:ss"
+                value-format="yyyy-MM-dd HH:mm:ss"
+                placeholder="选择日期"
+                style="width: 100%"
+                :picker-options="{
+                  disabledDate: (time) => time.getTime() > Date.now()
+                }"
+                disabled
+              >
+              </el-date-picker>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="检查结束时间" required prop="checkFinishTime">
+              <el-date-picker
+                v-model="details.checkFinishTime"
+                type="datetime"
+                format="yyyy-MM-dd HH:mm:ss"
+                value-format="yyyy-MM-dd HH:mm:ss"
+                placeholder="选择日期"
+                style="width: 100%"
+                :picker-options="{
+                  disabledDate: (time) => time.getTime() > Date.now()
+                }"
+                disabled
+              >
+              </el-date-picker>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row style="margin-bottom: 20px">
+          <el-col :span="8">
+            <el-form-item label="执行人" required prop="teamId">
+              <div style="display: flex">
+                <el-select
+                  v-model="details.teamId"
+                  placeholder="请选择班组"
+                  filterable
+                  style="width: 120px"
+                  disabled
+                >
+                  <el-option
+                    v-for="item in teamList"
+                    :label="item.name"
+                    :value="item.id"
+                    :key="item.id"
+                  >
+                  </el-option>
+                </el-select>
+                <el-select
+                  v-model="details.executeUsersIds"
+                  placeholder="请选择执行人"
+                  filterable
+                  multiple
+                  style="max-width: 100%"
+                  disabled
+                >
+                  <div class="checkboxWrapper">
+                    <el-checkbox> 全选 </el-checkbox>
+                  </div>
+                  <el-option
+                    v-for="item in teamUserList"
+                    :label="item.name"
+                    :value="item.id"
+                    :key="item.id"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="工时" required prop="duration">
+              <el-input
+                placeholder="请输入"
+                v-model="details.duration"
+                type="number"
+                min="0"
+                step="0.1"
+                disabled
+              >
+                <template slot="append">
+                  <div style="width: 40px; box-sizing: border-box">小时</div>
+                </template>
+              </el-input>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="检查有效期">
+              <el-input
+                placeholder="请输入"
+                v-model="details.checkValidity"
+                type="text"
+                disabled
+              >
+                <template slot="append">
+                  <div style="width: 40px; box-sizing: border-box">
+                    <el-form-item
+                      class="checkValidityUnit"
+                      style="margin-bottom: 0"
+                    >
+                      <DictSelection
+                        dictName="检查有效期单位"
+                        clearable
+                        v-model="details.checkValidityUnit"
+                        placeholder="单位"
+                        style="width: auto"
+                      >
+                      </DictSelection>
+                    </el-form-item>
+                  </div>
+                </template>
+              </el-input>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row style="margin-bottom: 20px">
+          <el-col v-if="details.recordRulesExecuteMethodName" :span="8">
+            <el-form-item label="执行方式">
+              <el-input
+                v-model="details.recordRulesExecuteMethodName"
+                placeholder="自动带出"
+                disabled
+              ></el-input>
+            </el-form-item>
+          </el-col>
+          <el-col v-if="details.reportWorkType == 2" :span="8">
+            <el-form-item label="产出物">
+              <el-select
+                v-model="details.outputType"
+                placeholder="请选择产出物类型"
+                filterable
+                style="width: 100%"
+                disabled
+              >
+                <el-option
+                  v-for="item in outputTypeList"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="结论" required prop="conclusion">
+              <div style="display: flex; align-items: center; height: 35px">
+                <el-radio-group v-model="details.conclusion">
+                  <el-radio :label="1">合格</el-radio>
+                  <el-radio :label="2">不合格</el-radio>
+                </el-radio-group>
+              </div>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </template>
+
+      <header-title title="关联产品" style="margin-top: 20px"></header-title>
+      <el-row style="margin-bottom: 20px">
         <el-col :span="8">
-          <el-form-item label="记录规则名称">
-            <el-input :value="details.ruleName" disabled></el-input>
+          <el-form-item label="生产工单号" prop="workOrderCode">
+            <el-input
+              v-model="details.workOrderCode"
+              placeholder="请选择生产工单"
+              readonly
+              disabled
+            />
           </el-form-item>
         </el-col>
         <el-col :span="8">
-          <el-form-item label="记录规则分类">
-            <DictSelection
-              dictName="记录规则类型"
-              clearable
-              v-model="details.recordRulesClassify"
+          <el-form-item
+            v-if="details.reportWorkType != 4"
+            label="工序名称"
+            prop="produceTaskId"
+          >
+            <el-select
+              v-model="details.produceTaskId"
+              placeholder="请选择工序"
+              style="width: 100%"
               disabled
             >
-            </DictSelection>
+              <el-option
+                v-for="item in produceTaskList"
+                :key="item.taskId"
+                :label="item.taskTypeName"
+                :value="item.sourceTaskId"
+              >
+              </el-option>
+            </el-select>
           </el-form-item>
         </el-col>
         <el-col :span="8">
-          <el-form-item label="车间区域">
+          <el-form-item label="牌号">
             <el-input
-              :value="details.workshopArea"
+              v-model="details.brandNo"
+              placeholder="牌号"
               disabled
-            ></el-input> </el-form-item
-        ></el-col>
+            ></el-input>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row style="margin-bottom: 20px">
         <el-col :span="8">
-          <el-form-item label="检查完成时间">
+          <el-form-item
+            label="产品编码"
+            :required="
+              details.recordRulesClassify != '4' && details.reportWorkType != 4
+            "
+            :prop="
+              details.recordRulesClassify != '4' && details.reportWorkType != 4
+                ? 'productCode'
+                : ''
+            "
+          >
             <el-input
-              :value="details.checkFinishTime"
+              v-model="details.productCode"
+              placeholder="请选择产品信息"
+              readonly
               disabled
-            ></el-input> </el-form-item
-        ></el-col>
+            >
+              <template #append>
+                <el-button size="small">选择产品</el-button>
+              </template>
+            </el-input>
+          </el-form-item>
+        </el-col>
         <el-col :span="8">
-          <el-form-item label="检查有效期">
+          <el-form-item label="产品名称" disabled>
             <el-input
-              placeholder="请输入"
-              v-model="details.checkValidity"
-              type="text"
-              disabled
+              v-model="details.productName"
+              placeholder="请选择产品信息"
+              readonly
             >
-              <template slot="append">
-                <div style="width: 40px; box-sizing: border-box">
-                  <el-form-item required>
-                    <DictSelection
-                      dictName="检查有效期单位"
-                      clearable
-                      v-model="details.checkValidityUnit"
-                      placeholder="单位"
-                      style="width: auto; box-sizing: border-box; height: 36px"
-                      disabled
-                    >
-                    </DictSelection>
-                  </el-form-item>
-                </div>
+              <template #append>
+                <el-button size="small">选择产品</el-button>
               </template>
-            </el-input> </el-form-item
-        ></el-col>
+            </el-input>
+          </el-form-item>
+        </el-col>
         <el-col :span="8">
-          <el-form-item label="结论">
-            <div style="display: flex; align-items: center; height: 36px">
-              <el-radio-group v-model="details.conclusion" disabled>
-                <el-radio :label="1">合格</el-radio>
-                <el-radio :label="2">不合格</el-radio>
-              </el-radio-group>
-            </div>
+          <el-form-item label="批次号" prop="batchNo">
+            <el-input
+              v-model="details.batchNo"
+              placeholder="批次号"
+              :disabled="Boolean(details.workOrderId)"
+            ></el-input>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row style="margin-bottom: 20px">
+        <el-col :span="8">
+          <el-form-item label="规格" prop="specification">
+            <el-input
+              v-model="details.specification"
+              placeholder="规格"
+              :disabled="Boolean(details.workOrderId)"
+            ></el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="型号" prop="productModel">
+            <el-input
+              v-model="details.productModel"
+              placeholder="型号"
+              :disabled="Boolean(details.workOrderId)"
+            ></el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item
+            label="要求生产数量"
+            :prop="
+              details.recordRulesClassify != '4' && details.reportWorkType != 4
+                ? 'formingNum'
+                : ''
+            "
+          >
+            <el-input
+              v-model.number="details.formingNum"
+              placeholder="要求生产数量"
+              :disabled="Boolean(details.workOrderId)"
+            >
+              <template slot="append" v-if="workOrderInfo">
+                {{ workOrderInfo.unit }}
+              </template>
+            </el-input>
           </el-form-item>
         </el-col>
-        <el-col :span="8"> </el-col>
       </el-row>
 
+      <material
+        v-if="
+          details.produceTaskId &&
+          details.produceTaskInstanceId &&
+          workOrderInfo &&
+          details.reportWorkType == 2 &&
+          details.itemType == 2
+        "
+        ref="materialRef"
+        :pickDetails.sync="details.pickDetails"
+        :outputDetails.sync="details.outputDetails"
+        :preOutputDetails="preOutputDetails"
+        :sumOutputDetails="sumOutputDetails"
+        :workOrderId="details.workOrderId"
+        :ruleId="details.ruleId"
+        :produceTaskId="details.produceTaskId"
+        :produceTaskName="details.produceTaskName"
+        :produceTaskInstanceId="details.produceTaskInstanceId"
+        :bomCategoryId="workOrderInfo.bomCategoryId"
+        :outputType="details.outputType"
+        :categoryId="workOrderInfo.categoryId"
+        :executeStatus="details.executeStatus"
+        @refresh="getListByWorkOrderId"
+      ></material>
+
       <header-title
         v-if="details.executeMethod == 2"
         title="检查项目"
       ></header-title>
+
+      <template v-if="details.itemType == 2 && details.reportWorkType == 4">
+        <el-tabs v-model="statisticsType" type="card">
+          <el-tab-pane
+            v-for="i in statisticsTypeList"
+            :label="i.label"
+            :name="i.value"
+            :key="i.value"
+          >
+          </el-tab-pane>
+        </el-tabs>
+        <statistics
+          ref="statisticsRef"
+          :details.sync="details.details"
+          :statisticsType="statisticsType"
+          :workOrderId="workOrderInfo ? workOrderInfo.id : ''"
+          :routingId="workOrderInfo ? workOrderInfo.produceRoutingId : ''"
+          :bomCategoryId="workOrderInfo ? workOrderInfo.bomCategoryId : ''"
+          :ruleId="details.ruleId"
+          :rulesDetailList="rulesDetailList"
+          :unit="workOrderInfo ? workOrderInfo.unit : ''"
+          :formingNum="workOrderInfo ? workOrderInfo.formingNum : 0"
+        ></statistics>
+      </template>
+
       <!-- 记录规则 -->
-      <div v-if="details.executeMethod == 2">
+      <div v-if="details.executeMethod == 2 && details.reportWorkType != 4">
         <ele-pro-table
           v-loading="loading"
           ref="table"
@@ -452,13 +827,27 @@
 
 <script>
   import dictMixins from '@/mixins/dictMixins';
-  import { getById } from '@/api/producetaskrecordrulesrecord/index.js';
+  import tableColumnsMixin from '@/mixins/tableColumnsMixin';
+  import {
+    getById,
+    getTaskInstanceList,
+    getTeam,
+    getWorkOrderById,
+    queryListByWorkOrderId
+  } from '@/api/producetaskrecordrulesrecord/index.js';
   import { getById as maintenanceGetById } from '@/api/maintenance/patrol_maintenance.js';
   import { getUserPage } from '@/api/system/organization';
+  import warehouseDefinitionApi from '@/api/warehouseManagement/warehouseDefinition.js';
+  import material from './material.vue';
+  import statistics from './statistics.vue';
 
   export default {
     name: 'editModal',
-    mixins: [dictMixins],
+    mixins: [dictMixins, tableColumnsMixin],
+    components: {
+      material,
+      statistics
+    },
     props: {
       // 业务id
       businessId: {
@@ -467,8 +856,78 @@
     },
     data() {
       const formBaseData = {
-        username: '',
-        email: ''
+        id: null,
+        code: '',
+        // 设备id
+        deviceId: null,
+        //设备名称
+        deviceName: '',
+        // 记录规则执行方式,参考字典项:produce_task_config_execute_method
+        executeMethod: '2',
+        // 执行状态
+        executeStatus: 0,
+        // 记录规则事项类型,参考字典项:record_rules_item_type
+        itemType: '2',
+        // 工序ID
+        produceTaskId: null,
+        produceTaskInstanceId: '',
+        produceTaskConfigId: '',
+        produceRoutingId: '',
+        produceRoutingName: '',
+        // 工序名称
+        produceTaskName: '',
+        // 记录规则报工类型,参考字典项:record_rules_report_work_type
+        reportWorkType: null,
+        // 规则id,包括事项规则id,记录规则id,根据事项类型区分
+        ruleId: null,
+        // 	规则名称
+        ruleName: '',
+        // 任务id
+        taskId: null,
+        itemTaskName: '',
+        // mes生产工单表工单号 code
+        workOrderCode: '',
+        // workOrderId mes生产工单表id
+        workOrderId: null,
+        // 产品编码
+        productCode: null,
+        // 产品型号
+        productModel: null,
+        // 产品名称
+        productName: null,
+        // 记录规则分类,字典项:record_sheet
+        recordRulesClassify: '',
+        // 产品批次号
+        batchNo: '',
+        // 牌号
+        brandNo: '',
+        // 产品规格
+        specification: '',
+        // 要求生产数量
+        formingNum: null,
+        // 是否临时执行
+        isTempRecord: 1,
+        // 记录规则详情
+        details: [],
+        // 物品清单
+        pickDetails: [],
+        // 本次产出明细
+        outputDetails: [],
+        executeUsersIds: [],
+        workshopAreaId: null,
+        workshopArea: '',
+        checkStartTime: null,
+        checkFinishTime: null,
+        executeUsersIds: [],
+        executeUsers: [],
+        teamId: null,
+        duration: null,
+        checkValidity: null,
+        checkValidityUnit: '',
+        recordRulesExecuteMethodName: '',
+        recordRulesExecuteMethodId: null,
+        conclusion: null,
+        outputType: 1
       };
 
       return {
@@ -496,7 +955,30 @@
         uerList: [],
         deviceList: [],
         // ruleInfo
-        ruleInfo: ''
+        ruleInfo: '',
+        workshopAreaList: [],
+        // 工单信息
+        workOrderInfo: null,
+        teamUserList: [],
+        teamList: [],
+        teamAllList: [],
+        //工序名称列表
+        produceTaskList: [],
+        outputTypeList: [
+          { label: '原材料', value: 1 },
+          { label: '在制品', value: 2 },
+          { label: 'BOM标准产出', value: 3 }
+        ],
+        // 1-成品统计,2-物料统计,3-工序统计"
+        statisticsType: '1',
+        statisticsTypeList: [
+          { label: '成品统计', value: '1' },
+          { label: '物料统计', value: '2' },
+          { label: '工序统计', value: '3' }
+        ],
+        preOutputDetails: [],
+        sumOutputDetails: [],
+        rulesDetailList: []
       };
     },
     computed: {
@@ -565,6 +1047,18 @@
     mounted() {
       console.log('this.businessId', this.businessId);
       this.getDatails(this.businessId);
+      this.getWorkshopArea();
+
+      if (localStorage.getItem('singleUserInfo') == '1') {
+        const data = JSON.parse(localStorage.getItem('chooseUserInfo'));
+        this.getTeamList(data.teamId);
+      } else {
+        console.log(
+          'this.$store.state.user.info.teamId',
+          this.$store.state.user.info.teamId
+        );
+        this.getTeamList(this.$store.state.user.info.teamId);
+      }
     },
     methods: {
       // 关闭时清理表单
@@ -579,11 +1073,46 @@
           const data = await getById(id);
           console.log('data 详情数据', data);
           data.recordRulesClassify = data.recordRulesClassify + '';
-          data.details = data.details.map((i) => {
-            i.toolNames = i.tools.map((i) => i.toolName).join(',');
-            return i;
-          });
+          data.itemType = data.itemType + '';
+          data.executeMethod = data.executeMethod + '';
+
+          // 获取工单详情
+          if (data.workOrderId) {
+            this.getWorkOrderById(data.workOrderId);
+          }
+
           this.details = data;
+
+          this.details.details = this.details.details.map((i) => {
+            i.checkUsersIds = i.checkUsers.map((j) => j.userId);
+            return {
+              ...i,
+              toolNames: i.tools.map((i) => i.toolName).join(',')
+            };
+          });
+          // 工时毫秒转小时
+          this.details.duration = (
+            this.details.duration /
+            (1000 * 60 * 60)
+          ).toFixed(2);
+          this.details.executeUsersIds = this.details.executeUsers.map(
+            (i) => i.userId
+          );
+          this.details.teamId =
+            this.details.executeUsers.length > 0
+              ? this.details.executeUsers[0].teamId
+              : '';
+          // 加载班组人员列表
+          if (this.details.teamId) {
+            const index = this.teamList.findIndex(
+              (item) => item.id == this.details.teamId
+            );
+            this.teamUserList = this.teamAllList[index];
+          }
+
+          this.getProductTaskList(this.details.workOrderId);
+          this.getListByWorkOrderId();
+
           this.loading = false;
         } catch (error) {
           this.loading = false;
@@ -634,6 +1163,178 @@
             this.uerList = res.list;
           }
         } catch (error) {}
+      },
+      // 查询车间区域
+      async getWorkshopArea() {
+        const data = await warehouseDefinitionApi.getFactoryarea({
+          pageNum: 1,
+          size: 999,
+          type: 3
+        });
+        console.log('list', data.list);
+        this.workshopAreaList = data.list;
+      },
+      async getTeamList(id) {
+        console.log('id', id);
+        if (id == '') return;
+        const ids = id.split(',');
+        console.log('ids', ids, id);
+        this.teamList = [];
+        this.teamUserList = [];
+        const list = ids.map((item) => {
+          if (item) {
+            return getTeam(item);
+          }
+          return [];
+        });
+
+        const dataList = await Promise.all(list);
+
+        dataList.forEach((item) => {
+          this.teamList.push({
+            name: item.name,
+            id: item.id
+          });
+
+          this.teamAllList.push(item.userVOList);
+        });
+      },
+      // 获取工序列表
+      async getProductTaskList(id) {
+        // 生产统计不关联工序
+        if (this.reportWorkType == 4) return;
+        const data = await getTaskInstanceList(id);
+        console.log('data 工序列表', data);
+        this.produceTaskList = data.filter(
+          (i) => i.taskId != '-2' && i.taskId != '-1'
+        );
+        if (this.details.produceTaskId) {
+          // 赋值工序实例id
+          this.details.produceTaskInstanceId = data.find(
+            (i) => i.sourceTaskId == this.details.produceTaskId
+          )?.taskId;
+        }
+      },
+      // 获取工单详情
+      async getWorkOrderById(id) {
+        const data = await getWorkOrderById(id);
+        console.log('工单详情', data);
+        this.workOrderInfo = data;
+      },
+      async getListByWorkOrderId() {
+        if (
+          !this.details.produceTaskId ||
+          this.details.reportWorkType != 2 ||
+          this.details.itemType != 2
+        ) {
+          return;
+        }
+        // 查询物料信息
+        const materialObject = await queryListByWorkOrderId({
+          workOrderId: this.details.workOrderId,
+          recordId: this.details.id,
+          produceTaskId: this.details.produceTaskId,
+          ruleId: this.details.ruleId
+        });
+
+        console.log('materialObject 物料信息', materialObject);
+        this.details.outputDetails = materialObject.outputDetails.map((i) => {
+          i.reportQuantityCopy = i.reportQuantity;
+
+          const sumItem = materialObject.sumOutputDetails.find(
+            (i) => i.categoryCode === i.categoryCode
+          );
+
+          if (sumItem) {
+            i.sumQualifiedQuantity = sumItem.sumQualifiedQuantity;
+            i.sumNoQualifiedQuantity = sumItem.sumNoQualifiedQuantity;
+            i.sumReportQuantity = sumItem.sumReportQuantity;
+          } else {
+            i.sumQualifiedQuantity = 0;
+            i.sumNoQualifiedQuantity = 0;
+            i.sumReportQuantity = 0;
+          }
+
+          return i;
+        });
+        const pickDetails = materialObject.pickDetails.map((i) => {
+          // 物料清单中如果存在则 selected 设置为 true
+          const any = this.details.outputDetails.find(
+            (j) => j.categoryCode === i.categoryCode
+          );
+          if (any) {
+            i.selected = true;
+          } else {
+            i.selected = false;
+          }
+
+          const sumItem = materialObject.sumOutputDetails.find(
+            (item) => i.categoryCode === item.categoryCode
+          );
+
+          if (sumItem) {
+            i.sumQualifiedQuantity = sumItem.sumQualifiedQuantity;
+            i.sumNoQualifiedQuantity = sumItem.sumNoQualifiedQuantity;
+            i.sumReportQuantity = sumItem.sumReportQuantity;
+          } else {
+            i.sumQualifiedQuantity = 0;
+            i.sumNoQualifiedQuantity = 0;
+            i.sumReportQuantity = 0;
+          }
+
+          return i;
+        });
+
+        const preOutputDetails = materialObject.preOutputDetails.map((i) => {
+          i.quantity = i.qualifiedQuantity;
+
+          // 物料清单中如果存在则 selected 设置为 true
+          const any = this.details.outputDetails.find(
+            (j) => j.categoryCode === i.categoryCode
+          );
+          if (any) {
+            i.selected = true;
+          } else {
+            i.selected = false;
+          }
+
+          const sumItem = materialObject.sumOutputDetails.find(
+            (item) => i.categoryCode === item.categoryCode
+          );
+
+          if (sumItem) {
+            i.sumQualifiedQuantity = sumItem.sumQualifiedQuantity;
+            i.sumNoQualifiedQuantity = sumItem.sumNoQualifiedQuantity;
+            i.sumReportQuantity = sumItem.sumReportQuantity;
+          } else {
+            i.sumQualifiedQuantity = 0;
+            i.sumNoQualifiedQuantity = 0;
+            i.sumReportQuantity = 0;
+          }
+
+          return i;
+        });
+
+        this.preOutputDetails = preOutputDetails.filter(
+          (i) => i.outputType != 1
+        );
+
+        this.sumOutputDetails = materialObject.sumOutputDetails;
+
+        this.details.pickDetails = [
+          ...preOutputDetails.filter((i) => i.outputType == 1),
+          ...pickDetails
+        ];
+
+        //  this.preOutputDetails 是否存在outputType=2的数据
+        const hasInProcess = this.preOutputDetails.some(
+          (i) => i.outputType == 2
+        );
+        if (hasInProcess) {
+          // 产出物为在制品
+          this.details.outputType = 2;
+          // 不可选择
+        }
       }
     }
   };

+ 1107 - 0
src/views/bpm/handleTask/components/mesBatchRecordManage/material.vue

@@ -0,0 +1,1107 @@
+<template>
+  <div>
+    <header-title title="物料清单"></header-title>
+
+    <ele-pro-table
+      ref="table"
+      row-key="id"
+      :columns="materialColumns"
+      :datasource="materialList"
+      cache-key="prenatal-examination-material-list-table-2511031522"
+      autoAmendPage
+      :needPage="false"
+      @refresh="$emit('refresh')"
+    >
+      <template v-slot:toolbar> </template>
+
+      <template v-slot:selectColumn="{ row }">
+        <el-checkbox
+          v-model="row.selected"
+          @change="selectedChange(row)"
+        ></el-checkbox>
+      </template>
+    </ele-pro-table>
+
+    <header-title title="产出清单" style="margin-top: 20px"></header-title>
+
+    <ele-pro-table
+      ref="table"
+      row-key="id"
+      :columns="outputDetailsColumns"
+      :datasource="localOutputDetails"
+      cache-key="prenatal-examination-output-list-table-2511031523"
+      autoAmendPage
+      :needPage="false"
+      @refresh="localOutputDetailsRefresh"
+    >
+      <template v-slot:reportQuantity="{ row }">
+        <div>
+          <el-input-number
+            size="small"
+            v-model.number="row.reportQuantity"
+            controls-position="right"
+            :min="0"
+            :max="reportQuantitymax(row)"
+            @change="reportQuantityChange(row)"
+          ></el-input-number>
+        </div>
+      </template>
+      <template v-slot:qualifiedQuantity="{ row }">
+        <el-input-number
+          size="small"
+          v-model.number="row.qualifiedQuantity"
+          controls-position="right"
+          :min="0"
+          :max="qualifiedQuantityMax(row)"
+          @change="qualifiedQuantityChange(row)"
+        ></el-input-number>
+      </template>
+      <template v-slot:noQualifiedQuantity="{ row }">
+        <el-input-number
+          size="small"
+          v-model.number="row.noQualifiedQuantity"
+          controls-position="right"
+          :min="0"
+          :max="noQualifiedQuantityMax(row)"
+          @change="noQualifiedQuantityChange(row)"
+        ></el-input-number>
+      </template>
+      <template v-slot:msg="{ row }">
+        <el-input
+          v-model="row.msg"
+          type="textarea"
+          rows="1"
+          autosize
+          @change="$emit('update:outputDetails', localOutputDetails)"
+        ></el-input>
+      </template>
+    </ele-pro-table>
+  </div>
+</template>
+
+<script>
+  import {
+    getMaterialQuotaInfo,
+    getCategoryAndLevelByCategoryId
+  } from '@/api/producetaskrecordrulesrecord/index';
+
+  export default {
+    components: {},
+    props: {
+      // 物料明细
+      pickDetails: {
+        type: Array,
+        default: () => []
+      },
+      // 本次产出明细
+      outputDetails: {
+        type: Array,
+        default: () => []
+      },
+      // 上到工序产出明细
+      preOutputDetails: {
+        type: Array,
+        default: () => []
+      },
+      sumOutputDetails: {
+        type: Array,
+        default: () => []
+      },
+      // 工单id
+      workOrderId: {
+        type: [String, Number],
+        required: true
+      },
+      // 规则id
+      ruleId: {
+        type: [String, Number],
+        required: false
+      },
+      // 工序id
+      produceTaskId: {
+        type: [String, Number],
+        required: true
+      },
+      // 工序实例id
+      produceTaskInstanceId: {
+        type: [String, Number],
+        required: true
+      },
+      // 工序名称
+      produceTaskName: {
+        type: String,
+        required: true
+      },
+      // BOM产品分类id
+      bomCategoryId: {
+        type: [String, Number],
+        required: true
+      },
+      // 产出类型 1-物料本身 2-在制品 3-BOM标准产出
+      outputType: {
+        type: [Number],
+        required: true
+      },
+      // 产品分类id
+      categoryId: {
+        type: [String, Number],
+        required: false
+      },
+      // 执行状态
+      executeStatus: {
+        type: [Number],
+        required: false
+      }
+    },
+    watch: {
+      pickDetails: {
+        handler(newVal) {
+          this.localPickDetails = JSON.parse(JSON.stringify(newVal)).map(
+            (i) => {
+              i.ruleId = this.ruleId;
+              i.bomCategoryId = this.bomCategoryId;
+              return i;
+            }
+          );
+        },
+        deep: true,
+        immediate: true
+      },
+      outputDetails: {
+        handler(newVal) {
+          this.localOutputDetails = JSON.parse(JSON.stringify(newVal)).map(
+            (i) => {
+              i.ruleId = this.ruleId;
+              i.outputType = this.outputType;
+              i.bomCategoryId = this.bomCategoryId;
+              i.id = null;
+              return i;
+            }
+          );
+        },
+        deep: true,
+        immediate: true
+      },
+      outputType: {
+        handler() {
+          // 重置产出明细
+          this.localOutputDetails = [];
+
+          this.localPickDetails = this.localPickDetails.map((item) => {
+            return {
+              ...item,
+              selected: false
+            };
+          });
+
+          // 根据不同的类型,重置物料明细
+          if (this.outputType == 1) {
+            this.$emit('update:outputDetails', this.localOutputDetails);
+            // 1-物料本身
+          } else if (this.outputType == 2) {
+            // 2-在制品
+            this.categoryInfoSetOutput();
+          } else {
+            // 3 BOM标准产出
+            this.setMaterialQuotaInfo();
+          }
+        }
+      },
+      ruleId: {
+        handler() {
+          // 更新物料明细的ruleId
+          this.localPickDetails = this.localPickDetails.map((item) => {
+            return {
+              ...item,
+              ruleId: this.ruleId
+            };
+          });
+          // 更新产出明细的ruleId
+          this.localOutputDetails = this.localOutputDetails.map((item) => {
+            return {
+              ...item,
+              ruleId: this.ruleId
+            };
+          });
+          // 通知父组件更新
+          this.$emit('update:pickDetails', this.localPickDetails);
+          this.$emit('update:outputDetails', this.localOutputDetails);
+        }
+      },
+      produceTaskId: {
+        handler() {
+          this.getMaterialQuotaInfo();
+          this.getCategoryAndLevelByCategoryId();
+        },
+        immediate: true
+      }
+    },
+    computed: {
+      // 物料清单(包含上到工序产出明细)
+      materialList() {
+        return [...this.preOutputDetails, ...this.localPickDetails];
+      },
+      // 物料清单表头
+      materialColumns() {
+        const list = [
+          {
+            width: 45,
+            type: 'index',
+            columnKey: 'index',
+            align: 'center'
+          },
+          {
+            label: '类型',
+            prop: 'categoryLevelNamePath',
+            minWidth: 120
+          },
+          {
+            label: '编码',
+            prop: 'categoryCode',
+            minWidth: 120
+          },
+          {
+            label: '名称',
+            prop: 'categoryName',
+            minWidth: 120
+          },
+          {
+            prop: 'model',
+            label: '型号',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'specifications',
+            label: '规格',
+            align: 'center',
+            width: 160,
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'brandNo',
+            label: '牌号',
+            align: 'center'
+          },
+          {
+            label: '工序名称',
+            prop: 'produceTaskName',
+            minWidth: 120
+          },
+          {
+            label: '数量',
+            prop: 'feedQuantity',
+            minWidth: 120,
+            formatter: (row) => {
+              return (row.quantity || 0) + (row.unit || '');
+            }
+          },
+          {
+            label: '当次消耗数量',
+            prop: '',
+            minWidth: 120,
+            formatter: (row) => {
+              // 产出为物料
+              if (this.outputType == 1) {
+                // 取值产出清单
+                const outputItem = this.localOutputDetails.find(
+                  (item) => item.categoryCode === row.categoryCode
+                );
+                return (
+                  (outputItem ? outputItem.reportQuantity : 0) +
+                  (row.unit || '')
+                );
+              } else {
+                // 产出为在制品 或 BOM标准产出
+                // 查询基数
+                if (!this.materialQuotaInfo) {
+                  return '0' + (row.unit || '');
+                }
+                // 当前行为在制品 和 BOM标准产出
+                const preOutputItem = this.localOutputDetails.find(
+                  (item) => item.categoryCode === row.categoryCode
+                );
+
+                if (preOutputItem) {
+                  return (preOutputItem.reportQuantity || 0) + (row.unit || '');
+                }
+
+                // 当前行为原材料
+                const materialItem = this.materialQuotaInfo.materialQuota.find(
+                  (item) => item.categoryCode === row.categoryCode
+                );
+                if (!materialItem) {
+                  return '0' + (row.unit || '');
+                }
+                // 需要的物料数量比例
+                let count =
+                  materialItem.count / this.materialQuotaInfo.baseCount;
+                // 产出清单的报工数量 当次报工+累计报工
+                const outputItem = this.localOutputDetails[0];
+                if (!outputItem) {
+                  return '0' + (row.unit || '');
+                }
+                // 当次消耗
+                let needQuantity = outputItem.reportQuantity * count;
+                return needQuantity + (row.unit || '');
+              }
+            }
+          },
+          {
+            label: '累计消耗数量',
+            prop: '',
+            minWidth: 120,
+            formatter: (row) => {
+              if (this.outputType == 1) {
+                const outputItem = this.localOutputDetails.find(
+                  (item) => item.categoryCode == row.categoryCode
+                );
+
+                // 查询其他工单sumOutputDetails中已产生的bom标准产出和在制品的累计消耗数量
+                let preCount = 0;
+                if (this.materialQuotaInfo) {
+                  const materialQuotaItem =
+                    this.materialQuotaInfo.materialQuota.find(
+                      (item) => item.categoryCode === row.categoryCode
+                    );
+
+                  if (!materialQuotaItem) {
+                    return 0 + (row.unit || '');
+                  }
+
+                  // 计算比例
+                  const proportion =
+                    materialQuotaItem.count / this.materialQuotaInfo.baseCount;
+
+                  if (this.materialQuotaInfo.standardOutput) {
+                    // BOM标准产出
+                    const preItem = this.sumOutputDetails.find(
+                      (item) =>
+                        item.categoryCode ===
+                        this.materialQuotaInfo.standardOutput.categoryCode
+                    );
+                    if (preItem) {
+                      preCount += preItem.sumReportQuantity * proportion;
+                    }
+                  }
+
+                  if (this.categoryInfo) {
+                    // 在制品
+                    const preItem = this.sumOutputDetails.find(
+                      (item) =>
+                        item.categoryCode === this.categoryInfo.categoryCode
+                    );
+                    if (preItem) {
+                      preCount += preItem.sumReportQuantity * proportion;
+                    }
+                  }
+                }
+
+                let count = 0;
+                if (outputItem) {
+                  if (this.executeStatus === 2) {
+                    // 已执行,处理重新一键报工时,sumReportQuantity包含了当前报工数量
+                    count =
+                      row.sumReportQuantity -
+                      (outputItem.reportQuantityCopy -
+                        outputItem.reportQuantity);
+                  } else {
+                    count = row.sumReportQuantity + outputItem.reportQuantity;
+                  }
+                } else {
+                  count = row.sumReportQuantity;
+                }
+
+                return count + preCount + (row.unit || '');
+              } else {
+                // 当前行为在制品 和 BOM标准产出
+                const output = this.localOutputDetails.find(
+                  (item) => item.categoryCode === row.categoryCode
+                );
+
+                if (output) {
+                  // 区分已执行状态
+                  if (this.executeStatus === 2) {
+                    // 已执行,处理重新一键报工时,sumReportQuantity包含了当前报工数量
+                    return (
+                      output.sumReportQuantity -
+                      (output.reportQuantityCopy - output.reportQuantity) +
+                      (row.unit || '')
+                    );
+                  }
+                  return (
+                    (output.sumReportQuantity || 0) +
+                    output.reportQuantity +
+                    (row.unit || '')
+                  );
+                }
+
+                // 产出为在制品 或 BOM标准产出 累计消耗数量
+                if (!this.materialQuotaInfo) {
+                  return row.sumReportQuantity + (row.unit || '');
+                }
+
+                const materialItem = this.materialQuotaInfo.materialQuota.find(
+                  (item) => item.categoryCode === row.categoryCode
+                );
+                if (!materialItem) {
+                  return row.sumReportQuantity + (row.unit || '');
+                }
+                // 计算在制 标准产出消耗原材料
+                const outputItem = this.localOutputDetails[0];
+                if (!outputItem) {
+                  return row.sumReportQuantity + (row.unit || '');
+                }
+                // 单位产出所需物料比例
+                const proportion =
+                  materialItem.count / this.materialQuotaInfo.baseCount;
+
+                // 查询其他工单sumOutputDetails中已产生的bom标准产出和在制品的累计消耗数量
+                let preCount = 0;
+                // 如果当前物料不是BOM标准产出,则需要加上BOM标准产出的消耗数量
+                if (
+                  outputItem.categoryCode !=
+                  this.materialQuotaInfo.standardOutput.categoryCode
+                ) {
+                  // BOM标准产出
+                  const preItem = this.sumOutputDetails.find(
+                    (item) =>
+                      item.categoryCode ===
+                      this.materialQuotaInfo.standardOutput.categoryCode
+                  );
+                  if (preItem) {
+                    preCount += preItem.sumReportQuantity * proportion;
+                  }
+                }
+                // 在制品
+                if (this.categoryInfo) {
+                  if (
+                    this.categoryInfo.categoryCode != outputItem.categoryCode
+                  ) {
+                    const preItem = this.sumOutputDetails.find(
+                      (item) =>
+                        item.categoryCode === this.categoryInfo.categoryCode
+                    );
+                    if (preItem) {
+                      preCount += preItem.sumReportQuantity * proportion;
+                    }
+                  }
+                }
+
+                let cumulativeOutputQuantity = 0;
+
+                if (this.executeStatus === 2) {
+                  // 已执行,处理重新一键报工时,sumReportQuantity包含了当前报工数量
+                  cumulativeOutputQuantity =
+                    outputItem.sumReportQuantity -
+                    (outputItem.reportQuantityCopy - outputItem.reportQuantity);
+                } else {
+                  cumulativeOutputQuantity =
+                    outputItem.sumReportQuantity + outputItem.reportQuantity;
+                }
+                const needQuantity = cumulativeOutputQuantity * proportion;
+
+                // 已消耗数量转换
+                // const sumReportQuantity = row.sumReportQuantity * proportion;
+                const sumReportQuantity = row.sumReportQuantity;
+
+                return (
+                  needQuantity + preCount + sumReportQuantity + (row.unit || '')
+                );
+              }
+            }
+          }
+        ];
+        if (this.outputType == 1) {
+          list.push({
+            prop: '',
+            label: '选择',
+            slot: 'selectColumn',
+            align: 'center',
+            fixed: 'right'
+          });
+        }
+        return list;
+      }
+    },
+
+    data() {
+      return {
+        localPickDetails: [],
+        localOutputDetails: [],
+        // bom配置的 标准产出信息
+        materialQuotaInfo: null,
+        // 在制品信息
+        categoryInfo: null,
+        // 产出清单表头
+        outputDetailsColumns: [
+          {
+            width: 45,
+            type: 'index',
+            columnKey: 'index',
+            align: 'center'
+          },
+          {
+            label: '类型',
+            prop: 'categoryLevelNamePath',
+            minWidth: 120
+          },
+          {
+            label: '编码',
+            prop: 'categoryCode',
+            minWidth: 120
+          },
+          {
+            label: '名称',
+            prop: 'categoryName',
+            minWidth: 120
+          },
+          {
+            prop: 'model',
+            label: '型号',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'specifications',
+            label: '规格',
+            align: 'center',
+            width: 160,
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'brandNo',
+            label: '牌号',
+            align: 'center'
+          },
+          {
+            label: '工序名称',
+            prop: 'produceTaskName',
+            minWidth: 120
+          },
+          {
+            label: '当次报工数量',
+            prop: 'reportQuantity',
+            minWidth: 160,
+            slot: 'reportQuantity'
+          },
+          {
+            label: '当次合格数量',
+            prop: 'qualifiedQuantity',
+            minWidth: 160,
+            slot: 'qualifiedQuantity'
+          },
+          {
+            label: '当次不合格数量',
+            prop: 'noQualifiedQuantity',
+            minWidth: 160,
+            slot: 'noQualifiedQuantity'
+          },
+          {
+            label: '不合格原因',
+            prop: 'msg',
+            minWidth: 160,
+            slot: 'msg'
+          },
+          {
+            label: '已报工数量',
+            prop: 'sumReportQuantity',
+            minWidth: 120,
+            formatter(row) {
+              return (row.sumReportQuantity || 0) + row.unit;
+            }
+          },
+          {
+            label: '合格总数',
+            prop: 'sumQualifiedQuantity',
+            minWidth: 120,
+            formatter(row) {
+              return (row.sumQualifiedQuantity || 0) + (row.unit || '');
+            }
+          },
+          {
+            label: '不合格总数',
+            prop: 'sumNoQualifiedQuantity',
+            minWidth: 120,
+            formatter(row) {
+              return (row.sumNoQualifiedQuantity || 0) + (row.unit || '');
+            }
+          }
+        ]
+      };
+    },
+    methods: {
+      // 选择物料
+      openMaterialModal() {
+        this.$refs.materialModalRef.open(
+          {
+            workOrderId: this.workOrderId,
+            ruleId: this.ruleId,
+            produceTaskId: this.produceTaskId
+          },
+          // ...this.localPickDetails, ...this.preOutputDetails
+          []
+        );
+      },
+      materialConfirm(data) {
+        // 选择物料后添加
+        console.log('data', data);
+
+        data.forEach((newItem) => {
+          const index = this.localPickDetails.findIndex(
+            (item) => item.categoryCode === newItem.categoryCode
+          );
+          if (index !== -1) {
+            // 存在则替换数量
+            this.localPickDetails[index].quantity = newItem.quantity;
+          } else {
+            const sumItem = this.sumOutputDetails.find(
+              (item) => item.categoryCode === newItem.categoryCode
+            );
+
+            let sumQualifiedQuantity = 0;
+            let sumNoQualifiedQuantity = 0;
+            let sumReportQuantity = 0;
+
+            if (sumItem) {
+              sumQualifiedQuantity = sumItem.sumQualifiedQuantity;
+              sumNoQualifiedQuantity = sumItem.sumNoQualifiedQuantity;
+              sumReportQuantity = sumItem.sumReportQuantity;
+            }
+
+            // 不存在则添加
+            this.localPickDetails.push({
+              ...newItem,
+              reportQuantity: 0,
+              ruleId: this.ruleId,
+              bomCategoryId: this.bomCategoryId,
+              produceTaskId: this.produceTaskId,
+              produceTaskInstanceId: this.produceTaskInstanceId,
+              produceTaskName: this.produceTaskName,
+              workOrderId: this.workOrderId,
+              sumQualifiedQuantity,
+              sumNoQualifiedQuantity,
+              sumReportQuantity
+            });
+          }
+        });
+
+        this.$emit('update:pickDetails', this.localPickDetails);
+      },
+      // bom标准产出赋值
+      setMaterialQuotaInfo() {
+        console.log('this.materialQuotaInfo', this.materialQuotaInfo);
+
+        if (this.materialQuotaInfo && this.materialQuotaInfo.standardOutput) {
+          const sumItem = this.sumOutputDetails.find(
+            (i) =>
+              i.categoryCode ===
+              this.materialQuotaInfo.standardOutput.categoryCode
+          );
+
+          let sumQualifiedQuantity = 0;
+          let sumNoQualifiedQuantity = 0;
+          let sumReportQuantity = 0;
+
+          if (sumItem) {
+            sumQualifiedQuantity = sumItem.sumQualifiedQuantity;
+            sumNoQualifiedQuantity = sumItem.sumNoQualifiedQuantity;
+            sumReportQuantity = sumItem.sumReportQuantity;
+          }
+
+          // 赋值产出
+          this.localOutputDetails = [
+            {
+              ...this.materialQuotaInfo.standardOutput,
+              id: null,
+              reportQuantity: 0,
+              reportQuantityCopy: 0,
+              qualifiedQuantity: 0,
+              noQualifiedQuantity: 0,
+              msg: '',
+              sumReportQuantity,
+              sumQualifiedQuantity,
+              sumNoQualifiedQuantity,
+              ruleId: this.ruleId,
+              outputType: this.outputType,
+              produceTaskId: this.produceTaskId,
+              produceTaskInstanceId: this.produceTaskInstanceId,
+              produceTaskName: this.produceTaskName,
+              workOrderId: this.workOrderId
+            }
+          ];
+          this.$emit('update:outputDetails', this.localOutputDetails);
+        } else {
+          this.$message.warning('未配置BOM标准产出信息');
+        }
+      },
+      // 获取bom的产出清单
+      async getMaterialQuotaInfo() {
+        if (this.bomCategoryId && this.produceTaskId) {
+          const data = await getMaterialQuotaInfo(
+            this.bomCategoryId,
+            this.produceTaskId
+          );
+          console.log('this.materialQuotaInfo', data);
+          if (JSON.stringify(data) === '{}') {
+            this.materialQuotaInfo = null;
+            return;
+          }
+          this.materialQuotaInfo = data;
+          console.log('this.materialQuotaInfo', this.materialQuotaInfo);
+        }
+      },
+      // qualifiedQuantityMax 最大合格数量
+      qualifiedQuantityMax(row) {
+        return row.reportQuantity;
+      },
+      noQualifiedQuantityMax(row) {
+        return row.reportQuantity;
+      },
+      selectedChange(row) {
+        console.log('row', row);
+        if (this.outputType != 1) return;
+
+        if (row.selected) {
+          // 判断是否存在
+          const index = this.localOutputDetails.findIndex(
+            (item) => item.categoryId === row.categoryId
+          );
+
+          if (index === -1) {
+            const sumItem = this.sumOutputDetails.find(
+              (i) => i.categoryCode === row.categoryCode
+            );
+
+            let sumQualifiedQuantity = 0;
+            let sumNoQualifiedQuantity = 0;
+            let sumReportQuantity = 0;
+
+            if (sumItem) {
+              sumQualifiedQuantity = sumItem.sumQualifiedQuantity;
+              sumNoQualifiedQuantity = sumItem.sumNoQualifiedQuantity;
+              sumReportQuantity = sumItem.sumReportQuantity;
+            }
+
+            // 不存在则添加
+            this.localOutputDetails.push({
+              ...row,
+              id: null,
+              reportQuantity: 0,
+              reportQuantityCopy: 0,
+              qualifiedQuantity: 0,
+              noQualifiedQuantity: 0,
+              msg: '',
+              sumReportQuantity,
+              sumQualifiedQuantity,
+              sumNoQualifiedQuantity,
+              ruleId: this.ruleId,
+              outputType: this.outputType,
+              bomCategoryId: this.bomCategoryId,
+              produceTaskId: this.produceTaskId,
+              produceTaskInstanceId: this.produceTaskInstanceId,
+              produceTaskName: this.produceTaskName,
+              workOrderId: this.workOrderId
+            });
+          } else {
+            this.$message.warning('该物料已存在于产出清单中');
+          }
+        } else {
+          // 取消选择则删除
+          this.localOutputDetails = this.localOutputDetails.filter(
+            (item) => !(item.categoryId === row.categoryId)
+          );
+        }
+
+        this.$emit('update:outputDetails', this.localOutputDetails);
+      },
+      // getCategoryAndLevelByCategoryId 获取产品 在制品信息
+      async getCategoryAndLevelByCategoryId() {
+        const data = await getCategoryAndLevelByCategoryId(this.categoryId);
+        this.categoryInfo = data;
+        console.log('this.categoryInfo 在制品信息', this.categoryInfo);
+      },
+      // 在制品信息 设置产出物
+      categoryInfoSetOutput() {
+        if (this.categoryInfo) {
+          const sumItem = this.sumOutputDetails.find(
+            (i) => i.categoryCode == this.categoryInfo.categoryCode
+          );
+
+          let sumQualifiedQuantity = 0;
+          let sumNoQualifiedQuantity = 0;
+          let sumReportQuantity = 0;
+
+          if (sumItem) {
+            sumQualifiedQuantity = sumItem.sumQualifiedQuantity;
+            sumNoQualifiedQuantity = sumItem.sumNoQualifiedQuantity;
+            sumReportQuantity = sumItem.sumReportQuantity;
+          }
+
+          this.localOutputDetails = [
+            {
+              ...this.categoryInfo,
+              id: null,
+              reportQuantity: 0,
+              reportQuantityCopy: 0,
+              qualifiedQuantity: 0,
+              noQualifiedQuantity: 0,
+              msg: '',
+              sumReportQuantity,
+              sumQualifiedQuantity,
+              sumNoQualifiedQuantity,
+              ruleId: this.ruleId,
+              outputType: this.outputType,
+              produceTaskId: this.produceTaskId,
+              produceTaskInstanceId: this.produceTaskInstanceId,
+              produceTaskName: this.produceTaskName,
+              workOrderId: this.workOrderId
+            }
+          ];
+        }
+
+        this.$emit('update:outputDetails', this.localOutputDetails);
+
+        console.log('this.localOutputDetails', this.localOutputDetails);
+      },
+      // 计算报工数量最大值
+      reportQuantitymax(row) {
+        if (this.outputType == 1) {
+          // 查询物料本身
+          const pickItem = this.localPickDetails.find(
+            (item) => item.categoryCode === row.categoryCode
+          );
+
+          // 查询其他工单sumOutputDetails中已产生的bom标准产出和在制品的累计消耗数量
+          let preCount = 0;
+          if (this.materialQuotaInfo) {
+            const materialQuotaItem = this.materialQuotaInfo.materialQuota.find(
+              (item) => item.categoryCode === row.categoryCode
+            );
+
+            if (materialQuotaItem) {
+              // 计算比例
+              const proportion =
+                materialQuotaItem.count / this.materialQuotaInfo.baseCount;
+
+              if (this.materialQuotaInfo.standardOutput) {
+                // BOM标准产出
+                const preItem = this.sumOutputDetails.find(
+                  (item) =>
+                    item.categoryCode ===
+                    this.materialQuotaInfo.standardOutput.categoryCode
+                );
+                if (preItem) {
+                  preCount += preItem.sumReportQuantity * proportion;
+                }
+              }
+
+              if (this.categoryInfo) {
+                // 在制品
+                const preItem = this.sumOutputDetails.find(
+                  (item) => item.categoryCode === this.categoryInfo.categoryCode
+                );
+                if (preItem) {
+                  preCount += preItem.sumReportQuantity * proportion;
+                }
+              }
+            }
+          }
+
+          // 未找到物料,则返回剩余数量
+          if (!pickItem) return pickItem.quantity - preCount;
+
+          // 已执行,处理重新一键报工时,sumReportQuantity包含了当前报工数量
+          if (this.executeStatus === 2) {
+            return (
+              pickItem.quantity -
+              (pickItem.sumReportQuantity - row.reportQuantityCopy) -
+              preCount
+            );
+          } else {
+            return pickItem.quantity - pickItem.sumReportQuantity - preCount;
+          }
+        }
+        if (row.outputType == 2 || row.outputType == 3) {
+          // 当前行为在制品 和 BOM标准产出
+          const preOutputItem = this.preOutputDetails.find(
+            (item) => item.categoryCode === row.categoryCode
+          );
+
+          // 存在则当前row为在制品或BOM标准产出
+          if (preOutputItem) {
+            // 区分已执行状态
+            if (this.executeStatus === 2) {
+              return (
+                preOutputItem.quantity -
+                (row.sumReportQuantity - row.reportQuantityCopy)
+              );
+            }
+            return preOutputItem.quantity - row.sumReportQuantity;
+          }
+
+          // 基于物料配额限制最大可报工数量(兼容已缓存/已保存一次的编辑场景,并区分已执行状态)
+          if (!this.materialQuotaInfo) return Infinity;
+          const quota = this.materialQuotaInfo.materialQuota || [];
+          if (!quota.length) return Infinity;
+
+          const isExecuted = this.executeStatus === 2;
+
+          const { baseCount } = this.materialQuotaInfo;
+          if (!baseCount) return Infinity;
+
+          // 计算每个物料的最大可报工数量
+          const maxQuantities = quota.map((item) => {
+            const pickItem = this.localPickDetails.find(
+              (p) => p.categoryCode === item.categoryCode
+            );
+            if (!pickItem) {
+              return Infinity;
+            }
+
+            // 单位产出所需物料比例
+            const proportion = item.count / baseCount;
+
+            // 查询其他工单 sumOutputDetails 中已产生的 BOM 标准产出和在制品的累计消耗数量
+            let preOutputConsumedProportion = 0;
+
+            // BOM标准产出
+            if (
+              this.materialQuotaInfo &&
+              this.materialQuotaInfo.standardOutput &&
+              row.categoryCode !==
+                this.materialQuotaInfo.standardOutput.categoryCode
+            ) {
+              const preStd = this.sumOutputDetails.find(
+                (s) =>
+                  s.categoryCode ===
+                  this.materialQuotaInfo.standardOutput.categoryCode
+              );
+              if (preStd) {
+                preOutputConsumedProportion +=
+                  preStd.sumReportQuantity * proportion;
+              }
+            }
+
+            // 在制品
+            if (
+              this.categoryInfo &&
+              row.categoryCode !== this.categoryInfo.categoryCode
+            ) {
+              const preWip = this.sumOutputDetails.find(
+                (s) => s.categoryCode === this.categoryInfo.categoryCode
+              );
+              if (preWip) {
+                preOutputConsumedProportion +=
+                  preWip.sumReportQuantity * proportion;
+              }
+            }
+
+            // 已执行,处理重新一键报工时,sumReportQuantity包含了当前报工数量
+            let consumedQuantity = isExecuted
+              ? row.sumReportQuantity - row.reportQuantityCopy
+              : row.sumReportQuantity;
+
+            // 已执行的比例换算,换算为需要多少物料数量
+            const consumedProportion = consumedQuantity * proportion;
+
+            // 最大可报工数量
+            let maxQuantity =
+              (pickItem.quantity -
+                consumedProportion -
+                preOutputConsumedProportion) /
+              proportion;
+
+            // 向下取整
+            return Math.floor(maxQuantity);
+          });
+
+          // 返回最小的那个物料的最大可报工数量
+          return Math.min(...maxQuantities);
+        }
+        return Infinity;
+      },
+      // 判断生成产出清单的物料数量是合格
+      async validateOutputQuantities() {
+        if (this.localOutputDetails.length == 0) {
+          this.$message.warning(`产出清单不能为空`);
+          return false;
+        }
+
+        for (const item of this.localOutputDetails) {
+          if (!item.reportQuantity) {
+            this.$message.warning(
+              `请填写产出清单【${item.categoryName}】的当次报工数量`
+            );
+            return false;
+          }
+        }
+
+        if (this.outputType == 1) {
+          if (
+            this.localPickDetails.length == 0 &&
+            this.preOutputDetails.length == 0
+          ) {
+            this.$message.warning(`物料清单不能为空`);
+            return false;
+          }
+
+          for (const item of this.localOutputDetails) {
+            const pickitem = this.localPickDetails.find(
+              (pick) => pick.categoryCode == item.categoryCode
+            );
+
+            const prePickitem = this.preOutputDetails.find(
+              (pick) => pick.categoryCode == item.categoryCode
+            );
+
+            if (pickitem == null && prePickitem == null) {
+              this.$message.warning(
+                `所需物料【${item.categoryName}】未在物料清单中找到`
+              );
+              return false;
+            }
+
+            if (item.reportQuantity > item.quantity) {
+              this.$message.error(
+                `物料【${item.categoryName}】的报工数量不能超过领料数量`
+              );
+              return false;
+            }
+          }
+        }
+
+        return true;
+      },
+      // 当次报工修改
+      reportQuantityChange(row) {
+        // 同步当前合格数量 和 不合格数量为0
+        // 默认全部合格
+        row.qualifiedQuantity = row.reportQuantity;
+        row.noQualifiedQuantity = 0;
+        this.$emit('update:outputDetails', this.localOutputDetails);
+      },
+      // qualifiedQuantityChange 当次合格数量变化 计算 noQualifiedQuantity不合格数量
+      qualifiedQuantityChange(row) {
+        row.noQualifiedQuantity = row.reportQuantity - row.qualifiedQuantity;
+        this.$emit('update:outputDetails', this.localOutputDetails);
+      },
+      // 不合格数量变化
+      noQualifiedQuantityChange(row) {
+        row.qualifiedQuantity = row.reportQuantity - row.noQualifiedQuantity;
+        this.$emit('update:outputDetails', this.localOutputDetails);
+      },
+      localOutputDetailsRefresh() {
+        if (this.outputType == 1) {
+          this.$emit('refresh', 'output');
+        } else if (this.outputType == 2) {
+          this.getCategoryAndLevelByCategoryId();
+        } else {
+          this.setMaterialQuotaInfo();
+        }
+      }
+    }
+  };
+</script>
+
+<style></style>

+ 1128 - 0
src/views/bpm/handleTask/components/mesBatchRecordManage/statistics.vue

@@ -0,0 +1,1128 @@
+<template>
+  <div>
+    <ele-pro-table
+      v-if="
+        statisticsType == 1 &&
+        localDetails[0] &&
+        localDetails[0].statisticsValue
+      "
+      ref="table"
+      :columns="typeOneColumns"
+      :datasource="localDetails[0].statisticsValue"
+      cacheKey="mes-statistics-type-1-2511041946"
+      :needPage="false"
+      show-summary
+      :summary-method="getSummaries"
+    >
+      <template v-slot:toolkit>
+        <el-button
+          v-if="localDetails[0].statisticsValue[0]"
+          type="primary"
+          @click="addRow"
+        >
+          添加
+        </el-button>
+      </template>
+
+      <template
+        v-for="(item, index) in localDetails[0].statisticsValue[0]"
+        v-slot:[`column_${index}`]="{ row }"
+      >
+        <div :key="`column_${index}`" class="column_content">
+          <!-- 文本 -->
+          <div v-if="row[index].paramType == 7 || row[index].isOnlyShow">
+            {{ row[index].value }}
+          </div>
+          <!-- 数值 -->
+          <template v-else-if="row[index].paramType == 1">
+            <div style="display: flex">
+              <el-input-number
+                v-model="row[index].value"
+                :placeholder="
+                  row[index].formula
+                    ? row[index].formula.replace(/[\[\]]/g, '')
+                    : `请输入${row[index].title}`
+                "
+                :disabled="Boolean(row[index].formula)"
+                :controls="false"
+                @change="handleDataChange(row)"
+              ></el-input-number>
+              <div style="width: 80px; height: 100%; flex-shrink: 0">
+                <DictSelection
+                  v-model="row[index].unit"
+                  dictName="工艺参数单位"
+                  placeholder="单位"
+                >
+                </DictSelection
+              ></div>
+            </div>
+          </template>
+          <!-- 公式 -->
+          <template v-else-if="row[index].paramType == 9">
+            <div style="display: flex">
+              <el-input
+                v-model="row[index].value"
+                :placeholder="
+                  row[index].formula
+                    ? row[index].formula.replace(/[\[\]]/g, '')
+                    : `请输入${row[index].title}`
+                "
+                disabled
+                @input="handleDataChange(row)"
+              ></el-input>
+              <div style="width: 80px; height: 100%; flex-shrink: 0">
+                <DictSelection
+                  v-model="row[index].unit"
+                  dictName="工艺参数单位"
+                  placeholder="单位"
+                >
+                </DictSelection
+              ></div>
+            </div>
+          </template>
+          <!-- 时间 -->
+          <el-date-picker
+            v-else-if="row[index].paramType == 5"
+            v-model="row[index].value"
+            type="datetime"
+            placeholder="选择日期时间"
+            style="width: 100%"
+          >
+          </el-date-picker>
+          <!-- 其他 -->
+          <el-input
+            v-else
+            v-model="row[index].value"
+            placeholder="请输入内容"
+          ></el-input>
+        </div>
+      </template>
+
+      <template v-slot:operation="{ row }">
+        <el-button type="text" size="small" @click="deleteRow(row)"
+          >删除</el-button
+        >
+      </template>
+    </ele-pro-table>
+    <div
+      v-if="
+        statisticsType == 1 &&
+        localDetails[0] &&
+        localDetails[0].statisticsValue
+      "
+      class=""
+    >
+    </div>
+    <ele-pro-table
+      v-if="
+        statisticsType == 2 &&
+        localDetails[1] &&
+        localDetails[1].statisticsValue
+      "
+      ref="table"
+      :columns="typeTwoColumns"
+      :datasource="typeTwoDataSource"
+      cacheKey="mes-statistics-type-2-2511041946"
+      :needPage="false"
+    >
+      <template v-slot:toolkit>
+        <div>
+          <el-input
+            v-model="keyword"
+            placeholder="请输入物料名称或编码"
+          ></el-input>
+        </div>
+      </template>
+
+      <template
+        v-for="(item, index) in localDetails[1].statisticsValue[0]"
+        v-slot:[`column_${index}`]="{ row }"
+      >
+        <div :key="`column_${index}`" class="column_content">
+          <!-- 文本 -->
+          <div v-if="row[index].paramType == 7 || row[index].isOnlyShow">
+            {{ row[index].value }}
+          </div>
+          <!-- 数值 -->
+          <template v-else-if="row[index].paramType == 1">
+            <div style="display: flex">
+              <el-input-number
+                v-model="row[index].value"
+                :placeholder="
+                  row[index].formula
+                    ? row[index].formula.replace(/[\[\]]/g, '')
+                    : `请输入${row[index].title}`
+                "
+                :disabled="Boolean(row[index].formula)"
+                :controls="false"
+                @change="handleDataChange(row)"
+              ></el-input-number>
+              <div style="width: 80px; height: 100%; flex-shrink: 0">
+                <DictSelection
+                  v-model="row[index].unit"
+                  dictName="工艺参数单位"
+                  placeholder="单位"
+                >
+                </DictSelection
+              ></div>
+            </div>
+          </template>
+          <!-- 公式 -->
+          <template v-else-if="row[index].paramType == 9">
+            <div style="display: flex">
+              <el-input
+                v-model="row[index].value"
+                :placeholder="
+                  row[index].formula
+                    ? row[index].formula.replace(/[\[\]]/g, '')
+                    : `请输入${row[index].title}`
+                "
+                disabled
+                @input="handleDataChange(row)"
+              ></el-input>
+              <div style="width: 80px; height: 100%; flex-shrink: 0">
+                <DictSelection
+                  v-model="row[index].unit"
+                  dictName="工艺参数单位"
+                  placeholder="单位"
+                >
+                </DictSelection
+              ></div>
+            </div>
+          </template>
+          <!-- 时间 -->
+          <el-date-picker
+            v-else-if="row[index].paramType == 5"
+            v-model="row[index].value"
+            type="datetime"
+            placeholder="选择日期时间"
+            style="width: 100%"
+          >
+          </el-date-picker>
+          <!-- 其他 -->
+          <el-input
+            v-else
+            v-model="row[index].value"
+            placeholder="请输入内容"
+          ></el-input>
+        </div>
+      </template>
+    </ele-pro-table>
+
+    <ele-pro-table
+      v-if="
+        statisticsType == 3 &&
+        localDetails[2] &&
+        localDetails[2].statisticsValue
+      "
+      ref="table"
+      :columns="typeThreeColumns"
+      :datasource="typeThreeDataSource"
+      cacheKey="mes-statistics-type-3-2511051037"
+      :needPage="false"
+    >
+      <template v-slot:toolkit>
+        <el-select
+          v-model="activeProductTaskName"
+          placeholder="请选择"
+          style="margin-right: 20px"
+        >
+          <el-option
+            v-for="item in productTaskNameList"
+            :key="item"
+            :label="item"
+            :value="item"
+          >
+          </el-option>
+        </el-select>
+      </template>
+
+      <template
+        v-for="(item, index) in localDetails[2].statisticsValue[0]"
+        v-slot:[`column_${index}`]="{ row }"
+      >
+        <div :key="`column_${index}`" class="column_content">
+          <!-- 文本 -->
+          <div v-if="row[index].paramType == 7 || row[index].isOnlyShow">
+            {{ row[index].value }}
+          </div>
+          <!-- 数值 -->
+          <template v-else-if="row[index].paramType == 1">
+            <div style="display: flex">
+              <el-input
+                v-model="row[index].value"
+                :placeholder="
+                  row[index].formula
+                    ? row[index].formula.replace(/[\[\]]/g, '')
+                    : `请输入${row[index].title}`
+                "
+                :disabled="Boolean(row[index].formula)"
+                :controls="false"
+                @change="handleDataChange(row)"
+              ></el-input>
+              <div style="width: 80px; height: 100%; flex-shrink: 0">
+                <DictSelection
+                  v-model="row[index].unit"
+                  dictName="工艺参数单位"
+                  placeholder="单位"
+                >
+                </DictSelection
+              ></div>
+            </div>
+          </template>
+          <!-- 公式 -->
+          <template v-else-if="row[index].paramType == 9">
+            <div style="display: flex">
+              <el-input
+                v-model="row[index].value"
+                :placeholder="
+                  row[index].formula
+                    ? row[index].formula.replace(/[\[\]]/g, '')
+                    : `请输入${row[index].title}`
+                "
+                disabled
+                @input="handleDataChange(row)"
+              ></el-input>
+              <div style="width: 80px; height: 100%; flex-shrink: 0">
+                <DictSelection
+                  v-model="row[index].unit"
+                  dictName="工艺参数单位"
+                  placeholder="单位"
+                >
+                </DictSelection
+              ></div>
+            </div>
+          </template>
+          <!-- 时间 -->
+          <el-date-picker
+            v-else-if="row[index].paramType == 5"
+            v-model="row[index].value"
+            type="datetime"
+            placeholder="选择日期时间"
+            style="width: 100%"
+          >
+          </el-date-picker>
+          <!-- 其他 -->
+          <el-input
+            v-else
+            v-model="row[index].value"
+            placeholder="请输入内容"
+          ></el-input>
+        </div>
+      </template>
+    </ele-pro-table>
+  </div>
+</template>
+
+<script>
+  import {
+    listProduceTaskAndFormNum,
+    getPickAndFeed,
+    inStoreageRecord
+  } from '@/api/producetaskrecordrulesrecord/index';
+
+  export default {
+    props: {
+      details: {
+        type: Array,
+        default: () => []
+      },
+      // 规则详情列表
+      rulesDetailList: {
+        type: Array,
+        default: () => []
+      },
+      statisticsType: {
+        type: String,
+        default: '1' // 1-成品统计,2-物料统计,3-工序统计
+      },
+      // 工艺路线id
+      routingId: {
+        type: [String, Number],
+        default: ''
+      },
+      // workOrderId 工单id
+      workOrderId: {
+        type: [String, Number],
+        default: ''
+      },
+      // bomCategoryId
+      bomCategoryId: {
+        type: [String, Number],
+        default: ''
+      },
+      //ruleId
+      ruleId: {
+        type: [String, Number],
+        default: ''
+      },
+      // 单位
+      unit: {
+        type: String,
+        default: ''
+      },
+      // 要求生产数量
+      formingNum: {
+        type: [Number],
+        default: 0
+      }
+    },
+    watch: {
+      details: {
+        deep: true,
+        immediate: true,
+        handler(val) {
+          // 已构建过统计数据
+          if (
+            val[0] &&
+            val[0].statisticsValue &&
+            val[0].statisticsValue.length > 0
+          ) {
+            this.localDetails = JSON.parse(JSON.stringify(val));
+          } else {
+            this.buildDetials(val);
+          }
+        }
+      }
+    },
+    computed: {
+      // 成品统计表头
+      typeOneColumns() {
+        if (
+          this.localDetails.length > 0 &&
+          this.localDetails[0].statisticsValue &&
+          this.localDetails[0].statisticsType == 1 &&
+          this.localDetails[0].statisticsValue[0]
+        ) {
+          const cList = this.localDetails[0].statisticsValue[0].map(
+            (item, index) => {
+              return {
+                label: item.title,
+                slot: `column_${index}`,
+                align: 'center',
+                minWidth: 200
+              };
+            }
+          );
+          cList.unshift({
+            width: 50,
+            type: 'index',
+            columnKey: 'index',
+            align: 'center'
+          });
+
+          cList.push({
+            width: 70,
+            label: '操作',
+            slot: 'operation',
+            fixed: 'right'
+          });
+
+          return cList;
+        } else {
+          return [];
+        }
+      },
+      // 物料统计表头
+      typeTwoColumns() {
+        if (
+          this.localDetails.length > 1 &&
+          this.localDetails[1].statisticsValue &&
+          this.localDetails[1].statisticsType == 2 &&
+          this.localDetails[1].statisticsValue[0]
+        ) {
+          const cList = this.localDetails[1].statisticsValue[0].map(
+            (item, index) => {
+              return {
+                label: item.title,
+                slot: `column_${index}`,
+                align: 'center',
+                minWidth: 200
+              };
+            }
+          );
+          cList.unshift({
+            width: 45,
+            type: 'index',
+            columnKey: 'index',
+            align: 'center'
+          });
+          return cList;
+        } else {
+          return [];
+        }
+      },
+      // 工序统计表头
+      typeThreeColumns() {
+        if (
+          this.localDetails.length > 2 &&
+          this.localDetails[2].statisticsValue &&
+          this.localDetails[2].statisticsType == 3 &&
+          this.localDetails[2].statisticsValue[0]
+        ) {
+          const cList = this.localDetails[2].statisticsValue[0].map(
+            (item, index) => {
+              return {
+                label: item.title,
+                slot: `column_${index}`,
+                align: 'center',
+                minWidth: 200
+              };
+            }
+          );
+          cList.unshift({
+            width: 45,
+            type: 'index',
+            columnKey: 'index',
+            align: 'center'
+          });
+          return cList;
+        } else {
+          return [];
+        }
+      },
+      // 工序名称列表
+      productTaskNameList() {
+        if (this.localDetails[2]?.statisticsValue) {
+          const list = this.localDetails[2].statisticsValue.map((rows) => {
+            return rows.find((i) => i.title === '工序名称')?.value || '';
+          });
+
+          list.unshift('全部'); // 全部选项
+          return list;
+        } else {
+          return [];
+        }
+      },
+      typeTwoDataSource() {
+        if (this.keyword.trim() === '') {
+          return this.localDetails[1].statisticsValue;
+        } else {
+          return this.localDetails[1].statisticsValue.filter((rows) => {
+            const nameItem = rows.find((i) => i.title === '物料名称');
+            const codeItem = rows.find((i) => i.title === '物料编码');
+            return (
+              (nameItem && nameItem.value.includes(this.keyword)) ||
+              (codeItem && codeItem.value.includes(this.keyword))
+            );
+          });
+        }
+      },
+      typeThreeDataSource() {
+        if (this.activeProductTaskName === '全部') {
+          return this.localDetails[2].statisticsValue;
+        } else {
+          return this.localDetails[2].statisticsValue.filter((rows) => {
+            const nameItem = rows.find((i) => i.title === '工序名称');
+            return nameItem && nameItem.value === this.activeProductTaskName;
+          });
+        }
+      }
+    },
+    data() {
+      return {
+        localDetails: [
+          {
+            ruleId: this.ruleId,
+            statisticsType: 1,
+            statisticsValue: []
+          },
+          {
+            ruleId: this.ruleId,
+            statisticsType: 2,
+            statisticsValue: []
+          },
+          {
+            ruleId: this.ruleId,
+            statisticsType: 3,
+            statisticsValue: []
+          }
+        ],
+        activeProductTaskName: '全部',
+        keyword: '',
+        _emitChangeTimer: null
+      };
+    },
+
+    methods: {
+      // 通知父组件数据变更(防抖 500ms)
+      emitChange() {
+        if (this._emitChangeTimer) clearTimeout(this._emitChangeTimer);
+        this._emitChangeTimer = setTimeout(() => {
+          this.$emit('update:details', this.localDetails);
+        }, 500);
+      },
+      // 构建统计数据
+      async buildDetials(detials) {
+        if (detials.length === 0) {
+          console.log('重置了');
+          this.localDetails = [
+            {
+              ruleId: this.ruleId,
+              statisticsType: 1,
+              statisticsValue: []
+            },
+            {
+              ruleId: this.ruleId,
+              statisticsType: 2,
+              statisticsValue: []
+            },
+            {
+              ruleId: this.ruleId,
+              statisticsType: 3,
+              statisticsValue: []
+            }
+          ];
+          return;
+        }
+        console.log('this.localDetails -- 开始构建', this.localDetails);
+
+        if (
+          (this.localDetails[0].statisticsValue &&
+            this.localDetails[0].statisticsValue.length == 0) ||
+          this.localDetails[0].ruleId != this.ruleId
+        ) {
+          const list = this.rulesDetailList
+            .filter((i) => i.statisticsType == 1)
+            .map((i) => {
+              return {
+                title: i.paramValue,
+                value: i.defaultValue,
+                formula: i.formula,
+                unit: i.unitName || this.unit,
+                paramType: i.paramType
+              };
+            });
+
+          // 已构建过统计数据
+          this.localDetails[0] = {
+            ...this.localDetails[0],
+            ruleId: this.ruleId,
+            statisticsType: 1,
+            statisticsValue: [list]
+          };
+        }
+        // 构建物料和工序数据
+        this.rebuildMaterialAndProcessData();
+      },
+      // 构建物料和工序数据
+      async rebuildMaterialAndProcessData() {
+        if (this.unit) {
+          // 存在单位则 更新statisticsType为1的单位
+          this.localDetails[0].statisticsValue.forEach((rows) => {
+            rows.forEach((item) => {
+              if (!item.unit) {
+                item.unit = this.unit;
+              }
+            });
+          });
+        }
+
+        let routingTaskList = [];
+        let pickAndFeed = [];
+
+        if (this.workOrderId) {
+          // 并发获取工序任务列表和物料数据
+          let [routingTaskReslut, pickAndFeedList, inStoreageRecordList] =
+            await Promise.all([
+              listProduceTaskAndFormNum({ workOrderId: this.workOrderId }),
+              getPickAndFeed({
+                workOrderId: this.workOrderId,
+                bomCategoryId: this.bomCategoryId
+              }),
+              inStoreageRecord({
+                workOrderId: this.workOrderId
+              })
+            ]);
+          pickAndFeed = pickAndFeedList || [];
+          routingTaskList = routingTaskReslut || [];
+          console.log('inStoreageRecordList', inStoreageRecordList);
+          if (inStoreageRecordList && inStoreageRecordList.length > 0) {
+            // 成品入库一行的记录
+            const rows = this.rulesDetailList
+              .filter((i) => i.statisticsType == 1)
+              .map((i) => {
+                return {
+                  title: i.paramValue,
+                  value: i.defaultValue,
+                  formula: i.formula,
+                  unit: i.unitName || this.unit,
+                  paramType: i.paramType
+                };
+              });
+
+            if (this.rulesDetailList.length > 0) {
+              const list = inStoreageRecordList.map((record) => {
+                const rowsCopy = JSON.parse(JSON.stringify(rows));
+                rowsCopy.forEach((item) => {
+                  if (item.title.includes('成品入库数')) {
+                    item.value = record.totalCount;
+                  } else if (
+                    item.title === '成品入库时间' ||
+                    item.title === '入库时间' ||
+                    item.title === '生产日期'
+                  ) {
+                    item.value = record.storageTime;
+                  }
+                  item.unit = record.measuringUnit || this.unit;
+                });
+                return rowsCopy;
+              });
+              // 赋值
+              this.localDetails[0].statisticsValue = list;
+              console.log(
+                'this.localDetails[0].statisticsValue 重新',
+                this.localDetails[0].statisticsValue
+              );
+            }
+          }
+        }
+
+        console.log('routingTaskList 工序数据', routingTaskList);
+        console.log('物料数据 pickAndFeed', pickAndFeed);
+
+        this.localDetails[1] = {
+          ...this.localDetails[1],
+          ruleId: this.ruleId,
+          statisticsType: 2,
+          statisticsValue: pickAndFeed.map((pick) => {
+            const data = this.rulesDetailList
+              .filter((i) => i.statisticsType == 2)
+              .map((i) => {
+                return {
+                  title: i.paramValue,
+                  value: i.defaultValue,
+                  formula: i.formula,
+                  unit: i.unitName || this.unit,
+                  paramType: i.paramType
+                };
+              });
+
+            data.unshift({
+              title: '领用量',
+              value: pick.pickQuantity,
+              formula: '',
+              unit: pick.pickUnit,
+              // 1数值类型
+              paramType: 1
+            });
+
+            data.unshift({
+              title: '使用量',
+              value: pick.feedQuantity,
+              formula: '',
+              unit: pick.feedUnit,
+              // 1数值类型
+              paramType: 1
+            });
+
+            data.unshift({
+              title: '物料编码',
+              value: pick.categoryCode,
+              formula: '',
+              unit: '',
+              // 仅展示不计算和输入
+              isOnlyShow: true,
+              // 7文本类型
+              paramType: 7
+            });
+
+            data.unshift({
+              title: '物料名称',
+              value: pick.categoryName,
+              formula: '',
+              unit: '',
+              // 仅展示不计算和输入
+              isOnlyShow: true,
+              // produceTaskName 工序名称
+              produceTaskName: pick.produceTaskName,
+              // 7文本类型
+              paramType: 7
+            });
+
+            return data;
+          })
+        };
+
+        this.localDetails[2] = {
+          ...this.localDetails[2],
+          ruleId: this.ruleId,
+          statisticsType: 3,
+          statisticsValue: routingTaskList.map((task) => {
+            const data = this.rulesDetailList
+              .filter((i) => i.statisticsType == 3)
+              .map((i) => {
+                return {
+                  title: i.paramValue,
+                  value: i.defaultValue,
+                  formula: i.formula,
+                  unit: i.unitName || this.unit,
+                  paramType: i.paramType
+                };
+              });
+
+            data.unshift({
+              title: '工序名称',
+              value: task.name || task.produceTaskName,
+              formula: '',
+              unit: '',
+              // 仅展示不计算和输入
+              isOnlyShow: true,
+              // 7文本类型
+              paramType: 7
+            });
+
+            // 接收
+            if (!isNaN(task.formedNum)) {
+              const item = data.find(
+                (i) => i.title === '接收量' || i.title === '接收数量'
+              );
+              if (item) {
+                item.value = task.formedNum;
+              }
+            }
+
+            // 不合格品
+            if (!isNaN(task.noQualifiedSum)) {
+              const item = data.find(
+                (i) => i.title == '不合格品量' || i.title == '不合格品数量'
+              );
+              if (item) {
+                item.value = task.noQualifiedSum;
+              }
+            }
+
+            // 合格品量
+            if (!isNaN(task.qualified)) {
+              const item = data.find(
+                (i) => i.title == '合格品量' || i.title == '合格品数量'
+              );
+              if (item) {
+                item.value = task.qualified;
+              }
+            }
+
+            return data;
+          })
+        };
+
+        console.log('this.localDetails', this.localDetails);
+
+        // 通知父组件数据变更
+        this.emitChange();
+      },
+      // 当数据变化时计算公式
+      handleDataChange(row) {
+        console.log('row 触发', row);
+        // todo 计算公式
+        // 寻找当前行的所有可计算项,进行计算更新
+        const formulaItmes = row.filter((i) => i.formula);
+
+        formulaItmes.forEach((formulaItem) => {
+          // 简单示例 公式为 [(][合格品量][+][取样量][)][/][接收量]
+          const tokens = formulaItem.formula
+            ? formulaItem.formula.match(/\[([^\]]+)\]/g) || []
+            : [];
+          let expr = '';
+          let invalid = false;
+          for (const token of tokens) {
+            if (invalid) break;
+            const content = token.slice(1, -1).trim();
+            if (['+', '-', '*', '/', '(', ')', '%'].includes(content)) {
+              expr += content;
+            } else {
+              const target = row.find((r) => r.title === content);
+              // 特殊处理:当标题为“要求生产数量”且当前行未找到对应项时,用 this.formingNum 参与计算
+              if (!target && content === '要求生产数量') {
+                const formingVal = Number(this.formingNum);
+                if (!Number.isFinite(formingVal)) {
+                  invalid = true;
+                  expr = '';
+                  break;
+                }
+                expr += formingVal;
+                continue;
+              }
+              // 若对应的项不存在 或 value为空 则取消计算
+              if (
+                !target ||
+                target.value === '' ||
+                target.value === null ||
+                target.value === undefined
+              ) {
+                invalid = true;
+                expr = '';
+                break;
+              }
+              const num = Number(target.value);
+              if (!Number.isFinite(num)) {
+                invalid = true;
+                expr = '';
+                break;
+              }
+              expr += num;
+            }
+          }
+          // 若无效则直接跳过后续计算 (expr 已被置空)
+          if (expr && /^[0-9+\-*/().\s]+$/.test(expr)) {
+            try {
+              const result = Function(`"use strict";return (${expr})`)();
+              let value =
+                Number.isFinite(result) && !Number.isNaN(result) ? result : '';
+
+              if (value !== '' && typeof value === 'number') {
+                const dec = value.toString().split('.')[1];
+                if (dec && dec.length > 4) {
+                  const intPart = Math.trunc(value);
+                  const truncated = dec.slice(0, 4);
+                  value = Number(intPart + '.' + truncated);
+                }
+              }
+
+              formulaItem.value = value;
+            } catch (e) {
+              formulaItem.value = '';
+            }
+          } else {
+            formulaItem.value = '';
+          }
+        });
+
+        // 通知父组件数据变更
+        this.emitChange();
+      },
+      // 验证所有行/列(排除仅展示项),收集缺失项并提示;返回布尔值
+      validateStatisticsFilled() {
+        return true; // 暂时不验证
+
+        if (
+          !Array.isArray(this.localDetails) ||
+          this.localDetails.length === 0
+        ) {
+          this.$message.error('统计数据为空');
+          return false;
+        }
+
+        const typeNameMap = {
+          1: '成品统计',
+          2: '物料统计',
+          3: '工序统计'
+        };
+
+        const missing = [];
+
+        this.localDetails.forEach((detail, detailIndex) => {
+          if (!detail || !detail.statisticsValue) return;
+
+          // 统一转成多行结构
+          const rows =
+            detail.statisticsType === 1
+              ? [detail.statisticsValue[0] || []]
+              : detail.statisticsValue || [];
+
+          rows.forEach((row, rowIndex) => {
+            // 记录行的标识(物料名称 / 工序名称)
+            let rowLabel = '';
+            if (detail.statisticsType === 2) {
+              const nameItem = row.find((i) => i && i.title === '物料名称');
+              if (nameItem)
+                rowLabel = `(${nameItem.value || '物料行' + (rowIndex + 1)})`;
+            } else if (detail.statisticsType === 3) {
+              const procItem = row.find((i) => i && i.title === '工序名称');
+              if (procItem)
+                rowLabel = `(${procItem.value || '工序行' + (rowIndex + 1)})`;
+            }
+
+            row.forEach((item, colIndex) => {
+              if (!item || item.isOnlyShow) return;
+              const val = item.value;
+              if (val === '' || val === null || val === undefined) {
+                missing.push(
+                  `${typeNameMap[detail.statisticsType]}${rowLabel} - 第${
+                    colIndex + 1
+                  }列【${item.title}】未填写`
+                );
+              }
+            });
+          });
+        });
+
+        if (missing.length > 0) {
+          // 过多时截断
+          const displayList =
+            missing.length > 1
+              ? missing.slice(0, 1).concat(`, 共有 ${missing.length} 项未填写`)
+              : missing;
+          this.$message.warning(`请完善统计数据:\n${displayList.join('\n')}`);
+          return false;
+        }
+        return true;
+      },
+      getSummaries() {
+        const sums = [];
+        // 优化:缓存公式解析、统一数值获取,减少重复正则与遍历
+        if (
+          !this.localDetails[0] ||
+          !this.localDetails[0].statisticsValue ||
+          this.localDetails[0].statisticsValue.length === 0
+        ) {
+          return sums;
+        }
+
+        const rows = this.localDetails[0].statisticsValue;
+        const firstRow = rows[0] || [];
+        sums.push('合计');
+
+        // 缓存结构:{ formulaString: [{type:'op'|'num'|'ref', value:'+'|number|'列标题'}] }
+        if (!this._formulaCache) this._formulaCache = Object.create(null);
+
+        const parseFormula = (formula) => {
+          if (!formula) return [];
+          if (this._formulaCache[formula]) return this._formulaCache[formula];
+
+          // 提取 [xxx] 片段
+          const rawTokens = formula.match(/\[([^\]]+)\]/g) || [];
+          const tokens = [];
+          rawTokens.forEach((token) => {
+            const content = token.slice(1, -1).trim();
+            // 运算符/括号
+            if (['+', '-', '*', '/', '(', ')', '%'].includes(content)) {
+              tokens.push({ type: 'op', value: content });
+            } else {
+              // 引用列标题或特殊内置
+              tokens.push({ type: 'ref', value: content });
+            }
+          });
+          this._formulaCache[formula] = tokens;
+          return tokens;
+        };
+
+        // 辅助:安全数值
+        const toNum = (v) => {
+          const n = Number(v);
+          return Number.isFinite(n) ? n : 0;
+        };
+
+        // 先累加所有“非公式数值列”
+        rows.forEach((row) => {
+          row.forEach((cell, colIndex) => {
+            const targetIndex = colIndex + 1;
+            if (sums[targetIndex] === undefined) sums[targetIndex] = '';
+
+            if (cell.isOnlyShow || cell.paramType !== 1) {
+              return;
+            }
+            if (cell.formula) {
+              // 公式列占位为 0,后续再算
+              if (sums[targetIndex] === '') sums[targetIndex] = 0;
+              return;
+            }
+            const num = Number(cell.value);
+            if (!Number.isFinite(num)) return;
+            if (typeof sums[targetIndex] !== 'number') sums[targetIndex] = 0;
+            sums[targetIndex] += num;
+          });
+        });
+
+        // 计算公式列(使用上面得到的合计值作为引用值)
+        firstRow.forEach((meta, colIndex) => {
+          if (!meta.formula) return;
+          const targetIndex = colIndex + 1;
+
+          const tokens = parseFormula(meta.formula);
+          if (!tokens.length) {
+            sums[targetIndex] = '';
+            return;
+          }
+
+          let expr = '';
+          let invalid = false;
+
+          for (const tk of tokens) {
+            if (invalid) break;
+            if (tk.type === 'op') {
+              expr += tk.value;
+              continue;
+            }
+            // 引用列/特殊引用
+            if (tk.value === '要求生产数量') {
+              const formingVal = Number(this.formingNum);
+              if (!Number.isFinite(formingVal)) {
+                invalid = true;
+              } else {
+                expr += formingVal;
+              }
+              continue;
+            }
+            const refColIdx = firstRow.findIndex((i) => i.title === tk.value);
+            if (refColIdx === -1) {
+              invalid = true;
+              break;
+            }
+            const refVal = sums[refColIdx + 1];
+            if (!Number.isFinite(Number(refVal))) {
+              invalid = true;
+              break;
+            }
+            expr += toNum(refVal);
+          }
+
+          if (invalid || !expr || !/^[0-9+\-*/().\s%]+$/.test(expr)) {
+            sums[targetIndex] = '';
+            return;
+          }
+
+          try {
+            let result = Function('"use strict";return (' + expr + ')')();
+            if (!Number.isFinite(result) || Number.isNaN(result)) {
+              sums[targetIndex] = '';
+            } else {
+              const dec = result.toString().split('.')[1];
+              if (dec && dec.length > 4) {
+                const intPart = Math.trunc(result);
+                const truncated = dec.slice(0, 4);
+                result = Number(intPart + '.' + truncated);
+              }
+              sums[targetIndex] = result;
+            }
+          } catch (e) {
+            sums[targetIndex] = '';
+          }
+        });
+
+        return sums;
+      },
+      // 添加行
+      addRow() {
+        const row = JSON.parse(
+          JSON.stringify(this.localDetails[0].statisticsValue[0])
+        );
+
+        row.forEach((item) => {
+          if (!item.isOnlyShow) {
+            item.value = '';
+          }
+        });
+
+        this.localDetails[0].statisticsValue.push(row);
+      },
+      deleteRow(row) {
+        // 最后一条不允许删除
+        if (this.localDetails[0].statisticsValue.length <= 1) {
+          this.$message.warning('至少保留一条统计数据');
+          return;
+        }
+        const index = this.localDetails[0].statisticsValue.indexOf(row);
+        this.localDetails[0].statisticsValue.splice(index, 1);
+        // 通知父组件数据变更
+        this.emitChange();
+      }
+    }
+  };
+</script>
+
+<style scoped lang="scss">
+  .column_content ::v-deep .el-input-group__append {
+    padding: 0;
+  }
+</style>