src/pages/productionDesign/bom/index.vue
@@ -33,9 +33,14 @@
            </view>
            <text class="item-id">{{ item.bomNo || "-" }}</text>
          </view>
          <up-tag :text="'V' + (item.version || '1.0')"
                  type="primary"
                  size="mini" />
          <view class="item-right">
            <up-tag :text="'V' + (item.version || '1.0')"
                    type="primary"
                    size="mini" />
            <up-tag :text="isEnabled(item) ? '启用' : '停用'"
                    :type="isEnabled(item) ? 'success' : 'info'"
                    size="mini" />
          </view>
        </view>
        <up-divider></up-divider>
        <view class="item-details">
@@ -57,6 +62,15 @@
                     size="small"
                     type="primary"
                     @click="goStructure(item)">查看详情</up-button>
          <up-button class="action-btn"
                     size="small"
                     :type="isEnabled(item) ? 'warning' : 'success'"
                     @click="toggleStatus(item)">{{ isEnabled(item) ? "停用" : "启用" }}</up-button>
          <up-button class="action-btn"
                     size="small"
                     type="primary"
                     plain
                     @click="openKitCheck(item)">齐套分析</up-button>
        </view>
      </view>
      <up-loadmore :status="pageStatus" />
@@ -66,13 +80,92 @@
      <up-empty text="暂无BOM数据"
                mode="list"></up-empty>
    </view>
    <!-- 齐套分析弹窗 -->
    <up-popup :show="showKitPopup"
              mode="bottom"
              round="10"
              @close="showKitPopup = false">
      <view class="kit-popup">
        <view class="popup-header">
          <text class="popup-title">齐套分析 - {{ currentBom.bomNo || "" }}</text>
        </view>
        <view class="kit-form">
          <text class="kit-form-label">需求数量</text>
          <view class="kit-form-input">
            <up-number-box v-model="demandQty"
                           :min="0"
                           :step="1"></up-number-box>
          </view>
          <up-button type="primary"
                     size="small"
                     :loading="kitLoading"
                     text="分析"
                     @click="handleKitCheck"></up-button>
        </view>
        <scroll-view scroll-y
                     class="kit-result">
          <view v-if="kitResult.length > 0">
            <view v-for="(row, index) in kitResult"
                  :key="index"
                  class="kit-item">
              <view class="kit-item-header">
                <text class="kit-name">{{ row.productName || "-" }}</text>
                <text class="kit-model">{{ row.model || "-" }}</text>
                <up-tag v-if="num(row.shortageQty) > 0"
                        text="缺口"
                        type="error"
                        size="mini" />
              </view>
              <view class="kit-grid">
                <view class="kit-cell">
                  <text class="kit-label">单位</text>
                  <text class="kit-value">{{ row.unit || "-" }}</text>
                </view>
                <view class="kit-cell">
                  <text class="kit-label">需求数量</text>
                  <text class="kit-value">{{ num(row.requiredQty) }}</text>
                </view>
                <view class="kit-cell">
                  <text class="kit-label">可用库存</text>
                  <text class="kit-value">{{ num(row.availableQty) }}</text>
                </view>
                <view class="kit-cell">
                  <text class="kit-label">缺口数量</text>
                  <text class="kit-value"
                        :class="{ danger: num(row.shortageQty) > 0 }">{{ num(row.shortageQty) }}</text>
                </view>
              </view>
            </view>
          </view>
          <view v-else
                class="kit-empty">
            <up-empty mode="data"
                      :text="kitDone ? '库存齐套,无缺口' : '请输入需求数量后点击分析'"></up-empty>
          </view>
        </scroll-view>
        <view class="filter-actions">
          <up-button type="primary"
                     size="default"
                     :disabled="!kitDone"
                     text="生成采购需求"
                     @click="handleGenerateDemand"></up-button>
        </view>
      </view>
    </up-popup>
  </view>
</template>
<script setup>
  import { reactive, ref } from "vue";
  import { onReachBottom, onShow } from "@dcloudio/uni-app";
  import { listPage } from "@/api/productionManagement/bom";
  import {
    listPage,
    changeStatus,
    kitCheck,
    generateDemand,
  } from "@/api/productionManagement/bom";
  import PageHeader from "@/components/PageHeader.vue";
  const queryParams = reactive({
    productName: "",
@@ -85,6 +178,15 @@
    size: 3,
    total: 0,
  });
  // 后端不返回 status 时按启用展示(兼容老数据)
  const isEnabled = item =>
    item.status === undefined ||
    item.status === null ||
    Number(item.status) === 1;
  // decimal 可能是字符串,统一转数字兜底
  const num = val => Number(val) || 0;
  const goBack = () => {
    uni.navigateBack();
@@ -125,7 +227,7 @@
        }
      })
      .catch(() => {
        uni.showToast({ title: "查询失败", icon: "error" });
        // 错误提示由 request 统一处理
        pageStatus.value = "loadmore";
      });
  };
@@ -142,6 +244,87 @@
        item.remark || ""
      )}&version=${encodeURIComponent(item.version || 1)}`,
    });
  };
  // 启用/停用,status=1 时后端自动停用同规格其它版本
  const toggleStatus = item => {
    const enabled = isEnabled(item);
    const nextStatus = enabled ? 0 : 1;
    const actionText = enabled ? "停用" : "启用";
    uni.showModal({
      title: "提示",
      content: `确定${actionText}该BOM版本吗?`,
      success: res => {
        if (!res.confirm) return;
        uni.showLoading({ title: "处理中...", mask: true });
        changeStatus({ id: item.id, status: nextStatus })
          .then(() => {
            uni.hideLoading();
            uni.showToast({ title: `${actionText}成功`, icon: "none" });
            handleSearch();
          })
          .catch(() => {
            uni.hideLoading();
          });
      },
    });
  };
  // --- 齐套分析 ---
  const showKitPopup = ref(false);
  const kitLoading = ref(false);
  const kitDone = ref(false);
  const currentBom = ref({});
  const demandQty = ref(1);
  const kitResult = ref([]);
  const openKitCheck = item => {
    currentBom.value = item;
    demandQty.value = 1;
    kitResult.value = [];
    kitDone.value = false;
    showKitPopup.value = true;
  };
  const handleKitCheck = () => {
    const qty = Number(demandQty.value);
    if (!Number.isFinite(qty) || qty <= 0) {
      uni.showToast({ title: "需求数量必须大于0", icon: "none" });
      return;
    }
    kitLoading.value = true;
    kitCheck({ bomId: currentBom.value.id, demandQty: qty })
      .then(res => {
        kitResult.value = res?.data || [];
        kitDone.value = true;
      })
      .catch(() => {
        // 错误提示由 request 统一处理
        kitResult.value = [];
        kitDone.value = false;
      })
      .finally(() => {
        kitLoading.value = false;
      });
  };
  const handleGenerateDemand = () => {
    uni.showLoading({ title: "处理中...", mask: true });
    generateDemand({
      bomId: currentBom.value.id,
      demandQty: Number(demandQty.value),
    })
      .then(res => {
        uni.hideLoading();
        uni.showModal({
          title: "提示",
          content: String(res?.data ?? "采购需求生成完成"),
          showCancel: false,
        });
      })
      .catch(() => {
        uni.hideLoading();
      });
  };
  onReachBottom(() => {
@@ -172,8 +355,92 @@
  }
  .action-btn {
    width: calc(50% - 15rpx);
    width: calc(33.33% - 10rpx);
    margin: 0 !important;
    margin-bottom: 15rpx !important;
  }
  // --- 齐套分析弹窗 ---
  .kit-popup {
    padding-bottom: 20rpx;
  }
  .kit-form {
    display: flex;
    align-items: center;
    gap: 16rpx;
    padding: 20rpx 30rpx;
    .kit-form-label {
      font-size: 28rpx;
      color: #333;
    }
    .kit-form-input {
      flex: 1;
    }
  }
  .kit-result {
    height: 600rpx;
    padding: 0 30rpx;
  }
  .kit-item {
    border: 1rpx solid #f0f0f0;
    border-radius: 12rpx;
    padding: 16rpx;
    margin-bottom: 16rpx;
  }
  .kit-item-header {
    display: flex;
    align-items: center;
    gap: 12rpx;
    margin-bottom: 12rpx;
    .kit-name {
      font-size: 28rpx;
      color: #333;
      font-weight: 500;
    }
    .kit-model {
      font-size: 24rpx;
      color: #999;
      flex: 1;
    }
  }
  .kit-grid {
    display: flex;
    flex-wrap: wrap;
    .kit-cell {
      width: 50%;
      display: flex;
      align-items: center;
      margin-bottom: 8rpx;
      .kit-label {
        font-size: 24rpx;
        color: #777;
        min-width: 110rpx;
      }
      .kit-value {
        font-size: 24rpx;
        color: #333;
        &.danger {
          color: #ee0a24;
          font-weight: 500;
        }
      }
    }
  }
  .kit-empty {
    padding: 40rpx 0;
  }
</style>