695593266@qq.com преди 3 дни
родител
ревизия
9c71d712ae

+ 20 - 11
src/views/produce/components/feeding/components/workOrderBom.vue

@@ -140,16 +140,16 @@
         </div>
 
         <div class="item rx-sc" v-if="isOrder">
-          <div class="rx ww45">
+          <div class="rx ww50">
             <div class="lable rx-cc">任务报工</div>
             <div class="content">
               报工数:{{ taskReoportNum || 0 }}{{ item.unit }},损耗数:{{
                 taskLossNum || 0
-              }}{{ item.unit }}
+              }}{{ item.unit }},已派单数:{{ assignNum || 0 }}{{ item.unit }}
             </div>
           </div>
 
-          <div class="rx ww55">
+          <div class="rx ww50">
             <div class="lable rx-cc">过程监控</div>
             <div class="content">
               报工数:{{ sumReportQuantity || 0 }}{{ item.unit }},合格数:{{
@@ -199,12 +199,18 @@
       }
     },
 
-    computed: {},
+    computed: {
+      clientEnvironmentId() {
+        return this.$store.state.user.info.clientEnvironmentId;
+      }
+    },
 
     data() {
       return {
         taskReoportNum: '',
         taskLossNum: '',
+        assignNum: '',
+        taskReportInfo: {},
         sumNoQualifiedQuantity: '',
         sumQualifiedQuantity: '',
         sumReportQuantity: ''
@@ -236,13 +242,16 @@
           workOrderId: this.item.workOrderId,
           taskId: this.item.currentTaskDiagram.sourceTaskId
         }).then((res) => {
-          if (Object.keys(res.data).length != 0) {
-            this.taskReoportNum = res.data.reportQuantity
-              ? res.data.reportQuantity
-              : 0;
-            this.taskLossNum = res.data.lossQuantity
-              ? res.data.lossQuantity
-              : 0;
+          const data = res.data || {};
+          if (Object.keys(data).length != 0) {
+            this.taskReportInfo = data;
+            this.$emit('task-report-change', data);
+            this.taskReoportNum = data.reportQuantity ? data.reportQuantity : 0;
+            this.taskLossNum = data.lossQuantity ? data.lossQuantity : 0;
+            this.assignNum = data.assignNum ? data.assignNum : 0;
+          } else {
+            this.taskReportInfo = {};
+            this.$emit('task-report-change', {});
           }
         });
       },

+ 51 - 1
src/views/produce/components/jobBooking/index.vue

@@ -109,6 +109,7 @@
           :item="item"
           :isReportWork="true"
           :isOrder="!taskObj.isOrderListData"
+          @task-report-change="setTaskReportInfo(index, $event)"
         ></workOrderBom>
 
         <!-- 报工时间 -->
@@ -751,7 +752,8 @@
       },
 
       clientEnvironmentId() {
-        return this.$store.state.user.info.clientEnvironmentId;
+        // return this.$store.state.user.info.clientEnvironmentId;
+        return 9;
       }
     },
 
@@ -787,6 +789,50 @@
     },
 
     methods: {
+      setTaskReportInfo(index, data) {
+        if (this.List[index]) {
+          this.$set(this.List[index], 'taskReportInfo', data || {});
+        }
+      },
+
+      validateTaskReportForOneClick() {
+        if (this.clientEnvironmentId != 9) {
+          return true;
+        }
+
+        for (const item of this.List) {
+          const taskReportInfo = item.taskReportInfo || {};
+          if (Object.keys(taskReportInfo).length == 0) {
+            continue;
+          }
+
+          if (taskReportInfo.isAssign != 1) {
+            continue;
+          }
+
+          if (taskReportInfo.reportCheck != 1) {
+            this.$message.warning(
+              `工单${item.code || ''}未完成任务报工,不能一键报工`
+            );
+            return false;
+          }
+
+          const workReportNum = this.add(
+            Number(item.workReportInfo?.formedNum || 0),
+            Number(item.workReportInfo?.notFormedNum || 0)
+          );
+          const taskReportNum = Number(taskReportInfo.reportQuantity || 0);
+          if (Math.abs(Number(workReportNum) - taskReportNum) > 0.000001) {
+            this.$message.warning(
+              `工单${item.code || ''}报工数量需等于任务报工数量`
+            );
+            return false;
+          }
+        }
+
+        return true;
+      },
+
       checkPack() {
         this.$refs.packagingGroupingRef[0].getData();
       },
@@ -1752,6 +1798,10 @@
       },
 
       async save(type) {
+        if (type == 2 && !this.validateTaskReportForOneClick()) {
+          return;
+        }
+
         const required = await parameterGetByCode({
           code: 'mes_report_engrave_required'
         }).then((res) => {

+ 491 - 13
src/views/produce/components/workOrderDetailPreview.vue

@@ -1,24 +1,98 @@
 <template>
   <div class="work-order-detail-preview">
-    <div class="preview-title">工单详情</div>
+    <div class="preview-header">
+      <div class="title-wrap">
+        <span class="title-icon"><i class="el-icon-document"></i></span>
+        <div>
+          <div class="preview-title">工单详情</div>
+          <div class="preview-subtitle" :title="mainCode">
+            {{ mainCode || '-' }}
+          </div>
+        </div>
+      </div>
+      <el-tag size="small" type="success" effect="plain">
+        {{ reportTypeText }}
+      </el-tag>
+    </div>
+
     <div class="preview-scroll">
       <el-empty
         v-if="!workOrderInfo || !hasBasicInfo"
         description="暂无工单数据"
       />
+
       <template v-else>
-        <info :workOrderInfo="workOrderInfo" />
+        <section class="hero-panel">
+          <div class="hero-main">
+            <div class="hero-label">产品名称</div>
+            <div
+              class="hero-title"
+              :title="displayValue(workOrderInfo.productName)"
+            >
+              {{ displayValue(workOrderInfo.productName) }}
+            </div>
+            <div class="hero-meta">
+              <span :title="displayValue(workOrderInfo.productCode)">
+                编码:{{ displayValue(workOrderInfo.productCode) }}
+              </span>
+              <span :title="displayValue(workOrderInfo.batchNo)">
+                批次:{{ displayValue(workOrderInfo.batchNo) }}
+              </span>
+            </div>
+          </div>
+          <div class="status-stack">
+            <span class="status-pill green">{{ outsourceStatusText }}</span>
+            <span class="status-pill blue">{{ entrustStatusText }}</span>
+          </div>
+        </section>
+
+        <section class="stat-grid">
+          <div v-for="item in statItems" :key="item.label" class="stat-card">
+            <div class="stat-label">{{ item.label }}</div>
+            <div class="stat-value" :title="item.value">{{ item.value }}</div>
+          </div>
+        </section>
+
+        <section class="progress-panel">
+          <div class="progress-head">
+            <span>生产进度</span>
+            <strong>{{ progressPercent }}%</strong>
+          </div>
+          <el-progress
+            :percentage="progressPercent"
+            :stroke-width="10"
+            :show-text="false"
+            color="#19a35b"
+          />
+        </section>
+
+        <section
+          v-for="group in detailGroups"
+          :key="group.title"
+          class="detail-section"
+        >
+          <div class="section-title">{{ group.title }}</div>
+          <div class="detail-grid">
+            <div
+              v-for="field in group.fields"
+              :key="field.label"
+              class="detail-item"
+            >
+              <div class="detail-label">{{ field.label }}</div>
+              <div class="detail-value" :title="field.value">
+                {{ field.value }}
+              </div>
+            </div>
+          </div>
+        </section>
       </template>
     </div>
   </div>
 </template>
 
 <script>
-  import info from '@/views/produceOrder/components/details/info.vue';
-
   export default {
     name: 'WorkOrderDetailPreview',
-    components: { info },
     props: {
       workOrderInfo: {
         type: Object,
@@ -29,6 +103,139 @@
       hasBasicInfo() {
         const w = this.workOrderInfo;
         return !!(w && (w.code || w.id || w.productCode));
+      },
+      mainCode() {
+        const w = this.workOrderInfo || {};
+        return w.code || w.workOrderCode || w.mesWorkOrderCode || '';
+      },
+      reportTypeText() {
+        const value = String(this.workOrderInfo.singleReport);
+        if (value === '0') return '批量报工';
+        if (value === '1') return '单个报工';
+        return '报工类型';
+      },
+      outsourceStatusText() {
+        return this.statusText(
+          this.workOrderInfo.outsourceStatus,
+          {
+            1: '未委外',
+            2: '委外中',
+            3: '完成委外'
+          },
+          '未委外'
+        );
+      },
+      entrustStatusText() {
+        return this.statusText(
+          this.workOrderInfo.entrustStatus,
+          {
+            1: '未请托',
+            2: '请托中',
+            3: '完成请托'
+          },
+          '未请托'
+        );
+      },
+      progressPercent() {
+        const total = Number(this.workOrderInfo.formingNum);
+        const done = Number(this.workOrderInfo.formedNum);
+        if (!total || total <= 0 || !done || done <= 0) return 0;
+        return Math.min(100, Math.round((done / total) * 100));
+      },
+      statItems() {
+        const w = this.workOrderInfo;
+        return [
+          {
+            label: '生产数量',
+            value: this.withUnit(w.formingNum, w.unit)
+          },
+          {
+            label: '已生产数量',
+            value: this.withUnit(w.formedNum, w.unit)
+          },
+          {
+            label: '重量',
+            value: this.withUnit(w.formingWeight, w.weightUnit)
+          },
+          {
+            label: '已生产重量',
+            value: this.withUnit(w.formedWeight || 0, w.weightUnit)
+          }
+        ];
+      },
+      detailGroups() {
+        const w = this.workOrderInfo;
+        return [
+          {
+            title: '基础信息',
+            fields: [
+              {
+                label: '生产订单号',
+                value: this.displayValue(w.apsWorkOrderCode)
+              },
+              {
+                label: '计划编码',
+                value: this.displayValue(w.productionPlanCode)
+              },
+              {
+                label: '工艺路线',
+                value: this.displayValue(w.produceRoutingName)
+              },
+              { label: '规格', value: this.displayValue(w.specification) },
+              { label: '型号', value: this.displayValue(w.model) },
+              { label: '牌号', value: this.displayValue(w.brandNo) }
+            ]
+          },
+          {
+            title: '生产信息',
+            fields: [
+              {
+                label: '生产编号',
+                value: this.displayValue(w.productionCodes)
+              },
+              { label: '颜色', value: this.displayValue(w.colorKey) },
+              { label: '机型', value: this.displayValue(w.modelKey) },
+              { label: '委外状态', value: this.outsourceStatusText },
+              { label: '请托状态', value: this.entrustStatusText },
+              { label: '报工类型', value: this.reportTypeText }
+            ]
+          },
+          {
+            title: '时间信息',
+            fields: [
+              {
+                label: '计划开始时间',
+                value: this.displayValue(w.planStartTime)
+              },
+              {
+                label: '计划结束时间',
+                value: this.displayValue(w.planCompleteTime)
+              },
+              { label: '实际开始时间', value: this.displayValue(w.startTime) },
+              {
+                label: '实际完成时间',
+                value: this.displayValue(w.completeTime)
+              },
+              { label: '创建时间', value: this.displayValue(w.createTime) }
+            ]
+          }
+        ];
+      }
+    },
+    methods: {
+      displayValue(value) {
+        return value === undefined || value === null || value === ''
+          ? '-'
+          : String(value);
+      },
+      withUnit(value, unit) {
+        const text = this.displayValue(value);
+        if (text === '-') return text;
+        return unit ? `${text} ${unit}` : text;
+      },
+      statusText(value, map, fallback) {
+        const key = String(value || '');
+        return map[key] || fallback;
       }
     }
   };
@@ -39,23 +246,294 @@
     height: 100%;
     display: flex;
     flex-direction: column;
-    background: #fff;
     box-sizing: border-box;
+    background: #f4f7f6;
+    color: #1f2d3d;
   }
 
-  .preview-title {
+  .preview-header {
     flex-shrink: 0;
-    padding: 12px 16px;
-    font-size: 15px;
-    font-weight: 600;
-    color: #157a2c;
-    border-bottom: 1px solid #ebeef5;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    gap: 12px;
+    padding: 14px 18px;
+    background: linear-gradient(135deg, #ffffff 0%, #f1faf5 100%);
+    border-bottom: 1px solid #dfe8e3;
+    box-shadow: 0 2px 8px rgba(20, 92, 55, 0.06);
+  }
+
+  .title-wrap {
+    display: flex;
+    align-items: center;
+    min-width: 0;
+    gap: 10px;
+  }
+
+  .title-icon {
+    width: 34px;
+    height: 34px;
+    flex-shrink: 0;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 8px;
+    background: #157a2c;
+    color: #fff;
+    font-size: 18px;
+    box-shadow: 0 6px 14px rgba(21, 122, 44, 0.18);
+  }
+
+  .preview-title {
+    font-size: 17px;
+    line-height: 22px;
+    font-weight: 700;
+    color: #126329;
+  }
+
+  .preview-subtitle {
+    max-width: 360px;
+    margin-top: 2px;
+    font-size: 12px;
+    line-height: 16px;
+    color: #6b7785;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
   }
 
   .preview-scroll {
     flex: 1;
     min-height: 0;
     overflow: auto;
-    padding: 12px 16px 16px;
+    padding: 14px 18px 18px;
+  }
+
+  .hero-panel,
+  .stat-card,
+  .progress-panel,
+  .detail-section {
+    border: 1px solid #e0e9e4;
+    border-radius: 8px;
+    background: #fff;
+    box-shadow: 0 6px 18px rgba(33, 64, 50, 0.06);
+  }
+
+  .hero-panel {
+    display: flex;
+    align-items: flex-start;
+    justify-content: space-between;
+    gap: 16px;
+    padding: 18px;
+    border-color: #cfe6d8;
+    background: linear-gradient(135deg, #ffffff 0%, #eefaf3 100%);
+  }
+
+  .hero-main {
+    min-width: 0;
+  }
+
+  .hero-label,
+  .stat-label,
+  .detail-label {
+    font-size: 12px;
+    color: #7a8794;
+  }
+
+  .hero-title {
+    margin-top: 6px;
+    font-size: 22px;
+    line-height: 30px;
+    font-weight: 700;
+    color: #152b1d;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  .hero-meta {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px 16px;
+    margin-top: 8px;
+    font-size: 13px;
+    color: #46515c;
+
+    span {
+      max-width: 260px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+  }
+
+  .status-stack {
+    display: flex;
+    flex-direction: column;
+    flex-shrink: 0;
+    gap: 8px;
+  }
+
+  .status-pill {
+    min-width: 72px;
+    padding: 5px 10px;
+    border-radius: 999px;
+    font-size: 12px;
+    line-height: 16px;
+    text-align: center;
+    font-weight: 600;
+  }
+
+  .status-pill.green {
+    color: #147b3a;
+    background: #e7f7ed;
+    border: 1px solid #b8e3c9;
+  }
+
+  .status-pill.blue {
+    color: #1f65a8;
+    background: #eaf4ff;
+    border: 1px solid #c6def7;
+  }
+
+  .stat-grid {
+    display: grid;
+    grid-template-columns: repeat(4, minmax(0, 1fr));
+    gap: 10px;
+    margin-top: 12px;
+  }
+
+  .stat-card {
+    min-width: 0;
+    padding: 13px 12px;
+  }
+
+  .stat-value {
+    margin-top: 8px;
+    font-size: 18px;
+    line-height: 24px;
+    font-weight: 700;
+    color: #17212b;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  .progress-panel {
+    margin-top: 12px;
+    padding: 14px 16px;
+  }
+
+  .progress-head {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 10px;
+    font-size: 13px;
+    color: #51606f;
+
+    strong {
+      font-size: 18px;
+      color: #157a2c;
+    }
+  }
+
+  .detail-section {
+    margin-top: 12px;
+    overflow: hidden;
+  }
+
+  .section-title {
+    position: relative;
+    padding: 12px 16px 10px 28px;
+    font-size: 15px;
+    line-height: 20px;
+    font-weight: 700;
+    color: #126329;
+    border-bottom: 1px solid #edf2ef;
+
+    &::before {
+      content: '';
+      position: absolute;
+      left: 16px;
+      top: 50%;
+      width: 4px;
+      height: 15px;
+      border-radius: 2px;
+      background: #157a2c;
+      transform: translateY(-50%);
+    }
+  }
+
+  .detail-grid {
+    display: grid;
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
+
+  .detail-item {
+    min-width: 0;
+    display: grid;
+    grid-template-columns: 104px minmax(0, 1fr);
+    align-items: center;
+    min-height: 42px;
+    border-right: 1px solid #edf2ef;
+    border-bottom: 1px solid #edf2ef;
+
+    &:nth-child(2n) {
+      border-right: none;
+    }
+
+    &:nth-last-child(-n + 2) {
+      border-bottom: none;
+    }
+  }
+
+  .detail-label {
+    height: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 0 8px;
+    background: #f7faf8;
+    border-right: 1px solid #edf2ef;
+    box-sizing: border-box;
+  }
+
+  .detail-value {
+    min-width: 0;
+    padding: 9px 12px;
+    font-size: 14px;
+    line-height: 20px;
+    color: #273444;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  ::v-deep .el-progress-bar__outer {
+    background-color: #e8eee9;
+  }
+
+  @media (max-width: 1500px) {
+    .stat-grid {
+      grid-template-columns: repeat(2, minmax(0, 1fr));
+    }
+
+    .detail-grid {
+      grid-template-columns: 1fr;
+    }
+
+    .detail-item,
+    .detail-item:nth-child(2n) {
+      border-right: none;
+    }
+
+    .detail-item:nth-last-child(-n + 2) {
+      border-bottom: 1px solid #edf2ef;
+    }
+
+    .detail-item:last-child {
+      border-bottom: none;
+    }
   }
 </style>

+ 1 - 2
src/views/produce/index.vue

@@ -1128,7 +1128,6 @@
         this.workListIds = data.ids;
         this.workData = data;
         this.checkProductionResult();
-        // this.refreshFirstArticlePickFeedStatus();
       },
 
       taskSelect(data) {
@@ -1302,7 +1301,7 @@
       },
 
       getFirstArticleWorkOrderId(row = {}) {
-        return row.workOrderId || row.mesWorkOrderId || row.id;
+        return row.id || row.workOrderId || row.mesWorkOrderId;
       },
 
       getFirstArticleTaskId(row = {}) {

+ 25 - 31
src/views/taskList/index.vue

@@ -287,7 +287,7 @@
   import qualityInspection from '@/views/produce/components/qualityInspection/index.vue';
   import ncReport from './components/ncReport.vue';
   import { parameterGetByCode } from '@/api/system/dictionary-data';
-  import { checkRepeatFeed } from '@/api/produce/index';
+  import { checkFeedStatus } from '@/api/produce/feeding';
 
   export default {
     components: {
@@ -937,6 +937,7 @@
         if (
           type === 'report' &&
           this.tabValue != '4' &&
+          this.$store.state.user.info?.clientEnvironmentId == 9 &&
           !(await this.validateTaskReportFeed(row))
         ) {
           return;
@@ -1024,37 +1025,30 @@
       },
 
       async validateTaskReportFeed(row) {
-        // if (!row || !row.workOrderId || !row.taskId) {
-        //   this.$message.warning('任务单缺少工单或工序信息,无法校验投料状态');
-        //   return false;
-        // }
+        if (!row || !row.mesWorkOrderId || !row.taskId) {
+          this.$message.warning('任务单缺少工单或工序信息,无法校验投料状态');
+          return false;
+        }
 
-        // this.loading = true;
-        // try {
-        //   const res = await checkRepeatFeed([
-        //     {
-        //       workOrderId: row.workOrderId,
-        //       currentTaskDiagram: {
-        //         taskId: row.taskId
-        //       }
-        //     }
-        //   ]);
-        //   const feedList = res?.data || [];
-        //   const hasFeed = feedList.some((item) => item.feedFormed != '0');
-
-        //   if (!hasFeed) {
-        //     this.$message.warning('请先完成投料后再报工');
-        //     return false;
-        //   }
-
-        //   return true;
-        // } catch (err) {
-        //   this.$message.error(err.message || '校验投料状态失败');
-        //   return false;
-        // } finally {
-        //   this.loading = false;
-        // }
-        return true; // 暂时不校验投料状态
+        this.loading = true;
+        try {
+          const hasFeed = await checkFeedStatus({
+            taskId: row.taskId,
+            workOrderId: row.mesWorkOrderId
+          });
+
+          if (hasFeed) {
+            this.$message.warning('请先完成投料后再报工');
+            return false;
+          }
+
+          return true;
+        } catch (err) {
+          // this.$message.error(err.message || '校验投料状态失败');
+          return false;
+        } finally {
+          this.loading = false;
+        }
       },
 
       getDecimalLength(num) {