Bladeren bron

feat(qualityBoard): 合格率图表统一工厂筛选与已报工口径并完善未设置项展示

lucw 1 dag geleden
bovenliggende
commit
3c2acf155f
2 gewijzigde bestanden met toevoegingen van 60 en 23 verwijderingen
  1. 17 3
      src/views/qualityBoard/components/drillConfig.js
  2. 43 20
      src/views/qualityBoard/index.vue

+ 17 - 3
src/views/qualityBoard/components/drillConfig.js

@@ -29,8 +29,12 @@ const disposeOptions = [
   { label: '处置完成', value: 2 }
 ];
 
-/** 记录方法:复用列表页常量,并补上后端可能返回的“单样自定义”(3),避免数值直接显示 */
-const recordingOptions = [...recordingMethodList, { label: '单样自定义', value: 3 }];
+/**
+ * 记录方法:复用列表页常量,并补上后端可能返回的“单样自定义”(3)
+ * 0 为“未设置”哨兵值,对应 recording_method 为空;后端收到 0 会转成 IS NULL 条件,
+ * 弹窗内选中 0 只看空记录,清空下拉即可回到全部记录
+ */
+const recordingOptions = [...recordingMethodList, { label: '单样自定义', value: 3 }, { label: '未设置', value: 0 }];
 
 /** 检测工单列表列(字段均来自 QualityWorkOrderVO,缺失字段已剔除) */
 const WORK_ORDER_COLUMNS = [
@@ -39,7 +43,17 @@ const WORK_ORDER_COLUMNS = [
   { prop: 'sourceCode', label: '来源单号', width: 150, formatter: (row) => row.planSourceCode || row.workOrderCode || '' },
   { prop: 'qualityType', label: '类型', width: 110, dict: '质检计划类型' },
   { prop: 'qualityMode', label: '检测方式', width: 100, dict: '取样类型' },
-  { prop: 'recordingMethod', label: '记录方法', width: 110, formatter: (row) => (recordingOptions.find((item) => item.value == row.recordingMethod) || {}).label || '' },
+  {
+    prop: 'recordingMethod',
+    label: '记录方法',
+    width: 110,
+    formatter: (row) => {
+      const hit = recordingOptions.find((item) => item.value == row.recordingMethod);
+      if (hit) return hit.label;
+      /* 空记录方法在图表中归为“未设置”,明细列保持同一口径 */
+      return row.recordingMethod === null || row.recordingMethod === undefined ? '未设置' : '';
+    }
+  },
   { prop: 'productCode', label: '物品编码', width: 120 },
   { prop: 'productName', label: '物品名称', width: 130 },
   { prop: 'specification', label: '规格', width: 110 },

+ 43 - 20
src/views/qualityBoard/index.vue

@@ -20,7 +20,7 @@
         <div class="qb-title__wing qb-title__wing--right" aria-hidden="true"><i></i><i></i><i></i></div>
       </div>
       <div class="qb-header-right">
-        <!-- 工厂维度:除“工厂合格率”外的所有图表、KPI、明细弹窗都按此工厂筛选 -->
+        <!-- 工厂维度:所有图表(含三张合格率图)、KPI、明细弹窗都按此工厂筛选 -->
         <el-select
           v-model="factoryId"
           class="qb-factory"
@@ -97,28 +97,28 @@
       </div>
     </div>
 
-    <!-- 行2:合格率三视图(工厂 / 班组 / 产线),时间维度统一跟随页面顶部 本月 / 本季度 / 本年 -->
+    <!-- 行2:合格率三视图(工厂 / 班组 / 产线),时间与工厂维度统一跟随页面顶部;口径均为已报工(status=1)工单 -->
     <div class="qb-row">
       <div class="qb-card qb-col-4">
         <div class="qb-card-head">
-          <span title="合格率 = 合格数量 ÷ 检验数量 × 100%">工厂合格率</span>
-          <span class="qb-tip">{{ rateRangeLabel }}</span>
+          <span title="合格率 = 合格数量 ÷ 检验数量 × 100%;仅统计已报工工单,未维护工厂的工单归入“未设置工厂”">工厂合格率</span>
+          <span class="qb-tip">{{ rateRangeLabel }} | 已报工</span>
         </div>
         <v-chart v-if="factoryRateOption" class="qb-chart" :option="factoryRateOption" autoresize />
         <div v-else class="qb-empty">暂无数据</div>
       </div>
       <div class="qb-card qb-col-4">
         <div class="qb-card-head">
-          <span title="合格率 = 合格数量 ÷ 检验数量 × 100%">班组合格率</span>
-          <span class="qb-tip">{{ rateRangeLabel }}</span>
+          <span title="合格率 = 合格数量 ÷ 检验数量 × 100%;仅统计已报工工单,未维护班组的工单归入“未设置班组”">班组合格率</span>
+          <span class="qb-tip">{{ rateRangeLabel }} | 已报工</span>
         </div>
         <v-chart v-if="teamRateOption" class="qb-chart" :option="teamRateOption" autoresize />
         <div v-else class="qb-empty">暂无数据</div>
       </div>
       <div class="qb-card qb-col-4">
         <div class="qb-card-head">
-          <span title="合格率 = 合格数量 ÷ 检验数量 × 100%">产线合格率</span>
-          <span class="qb-tip">{{ rateRangeLabel }}</span>
+          <span title="合格率 = 合格数量 ÷ 检验数量 × 100%;仅统计已报工工单,班组未维护产线的工单归入“未设置产线”">产线合格率</span>
+          <span class="qb-tip">{{ rateRangeLabel }} | 已报工</span>
         </div>
         <v-chart v-if="lineRateOption" class="qb-chart" :option="lineRateOption" autoresize />
         <div v-else class="qb-empty">暂无数据</div>
@@ -150,8 +150,8 @@
       </div>
       <div class="qb-card qb-col-4">
         <div class="qb-card-head">
-          <span>合格率 / 不良率趋势</span>
-          <span class="qb-tip">合格率按批次,不良率按数量</span>
+          <span>合格率 / 不合格率趋势</span>
+          <span class="qb-tip">均为批次口径:合格批次数 / 批次数</span>
         </div>
         <v-chart v-if="trendOption" class="qb-chart" :option="trendOption" autoresize />
         <div v-else class="qb-empty">暂无数据</div>
@@ -281,7 +281,7 @@
       <div class="qb-card qb-col-4">
         <div class="qb-card-head">
           <span>质量预警</span>
-          <span class="qb-tip">消息推送记录</span>
+          <span class="qb-tip">消息推送 + 阈值越界</span>
         </div>
         <div class="qb-alert">
           <div v-for="item in alertList" :key="item.id" class="qb-alert-item">
@@ -611,11 +611,26 @@ export default {
     trendOption() {
       if (!this.trend.length) return null;
       return {
-      /* 合格率青色 / 不良率橙色(HR 首页流动趋势同款对比色) */
+      /* 合格率青色 / 不合格率橙色(HR 首页流动趋势同款对比色) */
       color: ['#00eaff', '#ffb020'],
         grid: { left: 10, right: 20, top: 40, bottom: 10, containLabel: true },
-        tooltip: { trigger: 'axis' },
-        legend: { data: ['合格率', '不良率'], textStyle: { color: AXIS_COLOR }, top: 0 },
+        tooltip: {
+          trigger: 'axis',
+          // 展示分子分母,避免 16.67% 这类数值(来自未完成批次)无法解释
+          formatter: (params) => {
+            const idx = params && params[0] ? params[0].dataIndex : 0;
+            const row = this.trend[idx] || {};
+            const batchCount = Number(row.batchCount) || 0;
+            const qualifiedCount = Number(row.qualifiedCount) || 0;
+            const unqualifiedCount = Math.max(batchCount - qualifiedCount, 0);
+            return [
+              row.label || '',
+              `合格率:${row.qualificationRate || 0}%(${qualifiedCount}/${batchCount} 批)`,
+              `不合格率:${row.batchNoQualificationRate || 0}%(${unqualifiedCount}/${batchCount} 批)`
+            ].join('<br/>');
+          }
+        },
+        legend: { data: ['合格率', '不合格率'], textStyle: { color: AXIS_COLOR }, top: 0 },
         xAxis: {
           type: 'category',
           data: this.trend.map((item) => item.label),
@@ -641,7 +656,7 @@ export default {
             data: this.trend.map((item) => item.qualificationRate || 0)
           },
           {
-            name: '不良率',
+            name: '不合格率',
             type: 'line',
             smooth: true,
             symbol: 'circle',
@@ -649,7 +664,8 @@ export default {
             lineStyle: { width: 2, shadowBlur: 14, shadowColor: fade('#ffb020', 0.6) },
             itemStyle: { shadowBlur: 10, shadowColor: fade('#ffb020', 0.8) },
             areaStyle: { color: glowGradient('#ffb020'), opacity: 0.6 },
-            data: this.trend.map((item) => item.noQualificationRate || 0)
+            // 后端由合格率反推,保证与合格率相加恒为 100
+            data: this.trend.map((item) => item.batchNoQualificationRate || 0)
           }
         ]
       };
@@ -1015,13 +1031,13 @@ export default {
     },
     /**
      * 三个合格率接口共用同一时间条件,各自独立请求,单个失败只影响自己那张卡片
-     * 工厂合格率本身按工厂分组对比,不传工厂;班组 / 产线合格率按顶部工厂筛选
+     * 三张图均按顶部工厂筛选(工厂图直接按工单 factories_id 聚合,不再经班组反推)
      */
     fetchRateData() {
       const timeParam = this.buildRateRange();
       const factoryParam = this.factoryId ? { factoriesId: this.factoryId } : {};
       return Promise.all([
-        getFactoryRate(timeParam)
+        getFactoryRate({ ...timeParam, ...factoryParam })
           .then((list) => {
             this.factoryRateList = list || [];
           })
@@ -1197,11 +1213,13 @@ export default {
     /** 记录方法分布:点柱条按记录方法下钻 */
     handleMethodDrill(params) {
       const item = this.recordingMethodDist[params && params.dataIndex];
-      if (!item || item.code === undefined || item.code === null) {
+      if (!item) {
         this.openKpiDrill('completion');
         return;
       }
-      this.openKpiDrill('completion', { recordingMethod: Number(item.code) });
+      // code 为 0 表示“未设置”(recording_method 为空),后端收到 0 会转成 IS NULL 条件
+      const code = item.code === undefined || item.code === null ? 0 : Number(item.code);
+      this.openKpiDrill('completion', { recordingMethod: code });
     },
     /** 不良类型占比:台账主表无不良类型字段,点击扇区只打开不良明细 */
     handleDefectTypeDrill() {
@@ -2280,6 +2298,11 @@ export default {
   background: rgba(255, 181, 71, 0.2);
   color: #ffc978;
 }
+/* 合格率 / 不合格率阈值越界:按参数配置实时判定,与平台 1/2/3 级别配色区分开 */
+.qb-alert-level.lv11 {
+  background: rgba(255, 107, 129, 0.2);
+  color: #ff8497;
+}
 .qb-alert-content {
   flex: 1;
   overflow: hidden;