昨天 f770f1735c127574f14042aa34801ab0984f62f2
feat(productionOrder): 优化生产订单表格展示和交互体验

- 新增计划完成时间列的悬浮提示功能
- 优化当前工序显示,添加颜色对比度检测确保文字可读性
- 改进工艺路线进度条视觉效果,统一设计样式
- 添加交期预警的颜色标识和文字提示
- 调整表格列宽计算逻辑,优化布局表现
- 增加弹窗点击遮罩不关闭的功能配置
- 统一按钮颜色使用主题变量,提升一致性
- 扩展操作列宽度以容纳更多功能按钮
已修改3个文件
173 ■■■■■ 文件已修改
public/favicon.ico 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/logo/logo.png 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/index.vue 173 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/favicon.ico

src/assets/logo/logo.png

src/views/productionManagement/productionOrder/index.vue
@@ -78,6 +78,11 @@
                :selectable="row => !row.endOrder"
                @selection-change="handleSelectionChange"
                @pagination="pagination">
        <template #planCompleteTime="{ row }">
          <span :title="deliveryHint(row)">
            {{ row.planCompleteTime ? dayjs(row.planCompleteTime).format('YYYY-MM-DD') : '' }}
          </span>
        </template>
        <template #completionStatus="{ row }">
          <el-progress :percentage="toProgressPercentage(row?.completionStatus)"
                       :color="progressColor(toProgressPercentage(row?.completionStatus))"
@@ -89,10 +94,11 @@
                    type="success"
                    size="small">全部完成</el-tag>
            <div v-else
                 class="current-process-name">
                 class="current-process-name"
                 :title="`${getCurrentProcess(row).name} · ${getCurrentProcess(row).pct}%`">
              <span class="process-color-dot"
                    :style="{ background: getCurrentProcess(row).color || '#909399' }"></span>
              <span :style="getCurrentProcess(row).color ? { color: getCurrentProcess(row).color } : {}">{{ getCurrentProcess(row).name }}</span>
              <span :style="readableTextColor(getCurrentProcess(row).color) ? { color: readableTextColor(getCurrentProcess(row).color) } : {}">{{ getCurrentProcess(row).name }}</span>
            </div>
          </template>
          <span v-else>-</span>
@@ -105,18 +111,13 @@
                 class="process-step">
              <div class="step-content">
                <div class="step-circle"
                     :class="{ 'is-completed': item.percentage >= 100 }">
                  <span class="step-percentage"
                        :style="{ color: item.percentage >= 70 ? item.percentage >= 100 ? '#67c23a' : '#f56c6c' : '#000' }">{{ item.percentage }}%</span>
                </div>
                <div class="step-name">
                  <span v-if="item.color"
                        :style="{ display: 'inline-block', width: '8px', height: '8px', borderRadius: '50%', marginRight: '4px', background: item.color }"></span>
                  <span>{{ item.name }}</span>
                </div>
                     :title="`${item.name} ${item.percentage}%`"
                     :style="{ background: item.color || '#409EFF', opacity: item.percentage >= 100 ? 1 : item.percentage > 0 ? 0.6 : 0.25 }"></div>
                <div class="step-name">{{ item.name }}</div>
              </div>
              <div v-if="index < row.processRouteStatus.length - 1"
                   class="step-line"></div>
                   class="step-line"
                   :style="{ background: item.percentage >= 100 ? '#67c23a' : '#dcdfe6' }"></div>
            </div>
          </div>
          <span v-else>-</span>
@@ -125,7 +126,8 @@
    </div>
    <el-dialog v-model="bindRouteDialogVisible"
               title="绑定工艺路线"
               width="500px">
               width="500px"
               :close-on-click-modal="false">
      <el-form label-width="90px">
        <el-form-item label="工艺路线">
          <el-select v-model="bindForm.routeId"
@@ -151,7 +153,8 @@
    <!-- 来源数据弹窗 -->
    <el-dialog v-model="sourceDataDialogVisible"
               title="来源数据"
               width="1200px">
               width="1200px"
               :close-on-click-modal="false">
      <div v-if="sourceRowData"
           class="applyno-summary1">
        <div class="summary-item">
@@ -288,14 +291,15 @@
    total: 0,
  });
  // 每步固定占宽:名称区96px + 连接线34px,长名称换行,保证圆圈-连线节奏一致
  const PROCESS_STEP_WIDTH = 130;
  const processColumnWidth = computed(() => {
    if (!tableData.value || tableData.value.length === 0) return "200px";
    const maxProcesses = Math.max(
      ...tableData.value.map(row => row.processRouteStatus?.length || 0)
    );
    if (maxProcesses === 0) return "100px";
    // 每个工序圆圈 36px + 线条 30px = 66px,额外加 60px 边距和文字空间
    return `${maxProcesses * 66 + 60}px`;
    return `${maxProcesses * PROCESS_STEP_WIDTH + 40}px`;
  });
  const tableColumn = computed(() => [
@@ -309,6 +313,7 @@
      prop: "currentProcess",
      dataType: "slot",
      slot: "currentProcess",
      align: "center",
      width: 160,
    },
    {
@@ -332,6 +337,7 @@
          : val === 5
          ? "已结束"
          : "已取消",
      // 已结束为正常关闭(中性灰),已取消才是异常操作(红)
      formatType: val =>
        val === 1
          ? "primary"
@@ -340,8 +346,8 @@
          : val === 3
          ? "success"
          : val === 5
          ? "danger"
          : "info",
          ? "info"
          : "danger",
    },
    {
      label: "产品名称",
@@ -395,7 +401,8 @@
    {
      label: "计划完成时间",
      prop: "planCompleteTime",
      formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
      dataType: "slot",
      slot: "planCompleteTime",
      width: 120,
    },
    {
@@ -403,7 +410,7 @@
      label: "操作",
      align: "center",
      fixed: "right",
      width: 280,
      width: 420,
      operation: [
        {
          name: "工艺路线",
@@ -474,7 +481,7 @@
        {
          name: "生产追溯",
          type: "text",
          color: "#409eff",
          color: "var(--el-color-primary)",
          clickFun: row => {
            router.push({
              path: "/productionManagement/productionTraceability",
@@ -489,7 +496,7 @@
        {
          name: "结束订单",
          type: "text",
          color: "red",
          color: "var(--el-color-danger)",
          showHide: row => !row.endOrder,
          clickFun: row => {
            handleEndOrder(row);
@@ -537,6 +544,27 @@
    return "#67c23a";
  };
  // hex 色转 rgba(用于光晕/淡底),非 hex 返回空串
  const hexToRgba = (hex, alpha) => {
    if (typeof hex !== "string") return "";
    const m = hex.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
    if (!m) return "";
    let n = m[1];
    if (n.length === 3) n = n.split("").map(c => c + c).join("");
    const v = parseInt(n, 16);
    return `rgba(${(v >> 16) & 255}, ${(v >> 8) & 255}, ${v & 255}, ${alpha})`;
  };
  // 过浅的工序色在白底上文字不可读,回退常规文字色
  const isLightColor = hex => {
    const rgba = hexToRgba(hex, 1);
    if (!rgba) return false;
    const [r, g, b] = rgba.match(/\d+/g).map(Number);
    return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.72;
  };
  const readableTextColor = color =>
    !color || isLightColor(color) ? "" : color;
  // 当前工序:第一道完成度不足100%的工序;全部完成返回 done
  const getCurrentProcess = row => {
    const list = row.processRouteStatus;
@@ -547,7 +575,20 @@
      done: false,
      name: current.name || "未知工序",
      color: current.color || "",
      pct: toProgressPercentage(current.percentage),
    };
  };
  // 交期预警的文字提示(颜色之外的无障碍冗余指示)
  const deliveryHint = row => {
    if (!row.planCompleteTime || row.isFh) return "";
    const diff = row.deliveryDaysDiff;
    if (diff === 15) return "距交期还剩 15 天";
    if (diff === 10) return "距交期还剩 10 天";
    if (diff === 2) return "距交期仅剩 2 天,时间紧张";
    if (diff < 0) return `已逾期 ${-diff} 天,请尽快处理`;
    if (diff < 2) return `距交期仅剩 ${diff} 天,紧急`;
    return "";
  };
  // 添加表行类名方法
@@ -557,13 +598,13 @@
    const diff = row.deliveryDaysDiff;
    if (diff === 15) {
      return "yellow";
      return "due-15";
    } else if (diff === 10) {
      return "pink";
      return "due-10";
    } else if (diff === 2) {
      return "purple";
      return "due-2";
    } else if (diff < 2) {
      return "red";
      return "due-critical";
    }
  };
@@ -884,39 +925,44 @@
    gap: 8px;
  }
  :deep(.yellow) {
    background-color: #faf0de;
  // 交期临近:黄→橙→浅红→红 色相递进,全部保持文字可读
  :deep(.due-15) {
    background-color: #fdf6ec;
  }
  :deep(.pink) {
    background-color: #fae1de;
  :deep(.due-10) {
    background-color: #faecd8;
  }
  :deep(.red) {
    background-color: #f80202;
  :deep(.due-2) {
    background-color: #fde2e2;
  }
  :deep(.purple) {
    background-color: #f4defa;
  :deep(.due-critical) {
    background-color: #fbc4c4;
    td:first-child {
      box-shadow: inset 3px 0 0 #f56c6c;
    }
  }
  .table_list {
    margin-top: unset;
  }
  .current-process-name {
    display: flex;
    align-items: center;
    font-size: 12px;
    display: inline-flex;
    flex-direction: column;
    align-items: stretch; // 色条宽度由文字撑开,两者等宽
    padding: 4px 0;
    font-size: 14px;
    color: #303133;
  }
  .process-color-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    margin-right: 4px;
    flex-shrink: 0;
    .process-color-dot {
      display: block;
      height: 16px;
      border-radius: 4px;
      margin-bottom: 6px;
    }
  }
  .process-progress-container {
@@ -927,52 +973,43 @@
    .process-step {
      display: flex;
      align-items: center;
      align-items: flex-start;
      position: relative;
      .step-content {
        display: flex;
        flex-direction: column;
        align-items: center;
        width: 96px;
        flex-shrink: 0;
        z-index: 1;
        .step-circle {
          width: 36px;
          height: 36px;
          border-radius: 50%;
          border: 2px solid #409eff;
          display: flex;
          align-items: center;
          justify-content: center;
          background-color: #fff;
          box-sizing: border-box;
          margin-bottom: 4px;
          .step-percentage {
            font-size: 11px;
            font-weight: bold;
          }
          &.is-completed {
            border-color: #67c23a;
            .step-percentage {
              color: #67c23a;
            }
          }
        }
        .step-name {
          font-size: 12px;
          color: #606266;
          white-space: nowrap;
          line-height: 1.4;
          max-height: 34px; // 最多两行
          overflow: hidden;
          text-align: center;
          word-break: break-all;
        }
      }
      .step-line {
        width: 30px;
        height: 1px;
        width: 130px; // 贯穿相邻两圆心,两端被圆圈(z-index更高)遮挡
        height: 2px;
        border-radius: 1px;
        background-color: #dcdfe6;
        margin: 0 -2px;
        margin-top: -20px; // 向上偏移以对齐圆心
        flex-shrink: 0;
        margin: 17px -48px 0; // 线在34px槽位居中:(130-34)/2=48;顶边17px对准圆心
      }
    }
  }