zhangwencui
6 小时以前 4c8d18cc5ed8a7b0e220c91a858d16d0310896df
src/pages/productionDesign/bom/index.vue
@@ -57,6 +57,14 @@
                     size="small"
                     type="primary"
                     @click="goStructure(item)">查看详情</up-button>
          <up-button class="action-btn"
                     size="small"
                     type="warning"
                     @click="openEdit(item)">修改</up-button>
          <up-button class="action-btn"
                     size="small"
                     type="error"
                     @click="handleDelete(item)">删除</up-button>
        </view>
      </view>
      <up-loadmore :status="pageStatus" />
@@ -66,13 +74,119 @@
      <up-empty text="暂无BOM数据"
                mode="list"></up-empty>
    </view>
    <view class="fab-button"
          @click="openAdd">
      <up-icon name="plus"
               size="24"
               color="#ffffff"></up-icon>
    </view>
    <up-popup :show="showFormPopup"
              mode="bottom"
              round
              @close="closeFormPopup">
      <view class="popup-container">
        <view class="popup-header">
          <text class="popup-cancel"
                @click="closeFormPopup">取消</text>
          <text class="popup-title">{{ formMode === 'add' ? '新增BOM' : '修改BOM' }}</text>
          <text class="popup-confirm"
                @click="submitForm">确定</text>
        </view>
        <view class="popup-body">
          <up-form ref="bomFormRef"
                   :model="bomForm"
                   :rules="bomRules"
                   label-width="110">
            <up-form-item label="产品"
                          prop="productModelId"
                          required>
              <up-input v-model="bomForm.productName"
                        readonly
                        placeholder="点击选择产品"
                        @click="openProductPicker" />
              <template #right>
                <up-icon name="arrow-right"
                         @click="openProductPicker"></up-icon>
              </template>
            </up-form-item>
            <up-form-item label="规格型号">
              <up-input v-model="bomForm.productModelName"
                        readonly
                        placeholder="--" />
            </up-form-item>
            <up-form-item label="版本号"
                          prop="version"
                          required>
              <up-input v-model="bomForm.version"
                        placeholder="请输入版本号"
                        clearable />
            </up-form-item>
            <up-form-item label="备注"
                          prop="remark">
              <up-textarea v-model="bomForm.remark"
                           placeholder="请输入备注"
                           auto-height />
            </up-form-item>
          </up-form>
        </view>
      </view>
    </up-popup>
    <up-popup :show="showProductPicker"
              mode="bottom"
              round
              @close="showProductPicker = false">
      <view class="popup-container">
        <view class="popup-header">
          <text class="popup-cancel"
                @click="showProductPicker = false">取消</text>
          <text class="popup-title">选择产品</text>
          <text class="popup-confirm"
                @click="handleProductSearch">搜索</text>
        </view>
        <view class="popup-body">
          <view class="picker-search">
            <up-input v-model="productQuery.productName"
                      placeholder="产品名称"
                      clearable
                      @change="handleProductSearch" />
            <up-input v-model="productQuery.model"
                      placeholder="规格型号"
                      clearable
                      @change="handleProductSearch" />
          </view>
          <scroll-view scroll-y
                       class="picker-list"
                       @scrolltolower="loadMoreProducts">
            <view v-for="row in productList"
                  :key="row.id"
                  class="picker-item"
                  @click="selectProduct(row)">
              <view class="picker-item__title">
                <text>{{ row.productName || '-' }}</text>
              </view>
              <view class="picker-item__sub">
                <text>{{ row.model || '-' }}</text>
                <text class="picker-item__unit">{{ row.unit || '-' }}</text>
              </view>
            </view>
            <up-loadmore :status="productPageStatus" />
          </scroll-view>
        </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,
    add,
    update,
    batchDelete,
    getProductList,
  } from "@/api/productionManagement/bom";
  const queryParams = reactive({
    productName: "",
@@ -85,6 +199,34 @@
    size: 3,
    total: 0,
  });
  const showFormPopup = ref(false);
  const formMode = ref("add");
  const bomFormRef = ref(null);
  const bomForm = reactive({
    id: undefined,
    productName: "",
    productModelName: "",
    productModelId: "",
    remark: "",
    version: "",
  });
  const bomRules = {
    productModelId: [{ required: true, message: "请选择产品", trigger: "blur" }],
    version: [{ required: true, message: "请输入版本号", trigger: "blur" }],
  };
  const showProductPicker = ref(false);
  const productQuery = reactive({
    productName: "",
    model: "",
  });
  const productList = ref([]);
  const productPage = reactive({
    current: 1,
    size: 20,
    total: 0,
  });
  const productPageStatus = ref("loadmore");
  const goBack = () => {
    uni.navigateBack();
@@ -144,6 +286,153 @@
    });
  };
  const openAdd = () => {
    formMode.value = "add";
    Object.assign(bomForm, {
      id: undefined,
      productName: "",
      productModelName: "",
      productModelId: "",
      remark: "",
      version: "",
    });
    showFormPopup.value = true;
  };
  const openEdit = row => {
    formMode.value = "edit";
    Object.assign(bomForm, {
      id: row.id,
      productName: row.productName || "",
      productModelName: row.productModelName || "",
      productModelId: row.productModelId || "",
      remark: row.remark || "",
      version: row.version || "",
    });
    showFormPopup.value = true;
  };
  const closeFormPopup = () => {
    showFormPopup.value = false;
  };
  const submitForm = () => {
    if (!bomFormRef.value) return;
    bomFormRef.value.validate(valid => {
      if (!valid) return;
      const payload = { ...bomForm };
      const req = formMode.value === "add" ? add(payload) : update(payload);
      req
        .then(res => {
          if (res && res.code !== undefined && res.code !== 200) {
            uni.showToast({
              title: res.msg || "提交失败",
              icon: "none",
            });
            return;
          }
          uni.showToast({
            title: "提交成功",
            icon: "success",
          });
          closeFormPopup();
          handleSearch();
        })
        .catch(() => {
          uni.showToast({
            title: "提交失败",
            icon: "error",
          });
        });
    });
  };
  const handleDelete = row => {
    if (!row?.id) return;
    uni.showModal({
      title: "提示",
      content: "确认删除该BOM?",
      confirmText: "确认",
      cancelText: "取消",
      success: res => {
        if (!res.confirm) return;
        batchDelete([row.id])
          .then(result => {
            if (result && result.code !== undefined && result.code !== 200) {
              uni.showToast({
                title: result.msg || "删除失败",
                icon: "none",
              });
              return;
            }
            uni.showToast({
              title: "删除成功",
              icon: "success",
            });
            handleSearch();
          })
          .catch(() => {
            uni.showToast({
              title: "删除失败",
              icon: "error",
            });
          });
      },
    });
  };
  const openProductPicker = () => {
    showProductPicker.value = true;
    handleProductSearch();
  };
  const handleProductSearch = () => {
    productPage.current = 1;
    productPageStatus.value = "loadmore";
    productList.value = [];
    loadMoreProducts();
  };
  const loadMoreProducts = () => {
    if (
      productPageStatus.value === "loading" ||
      productPageStatus.value === "nomore"
    ) {
      return;
    }
    productPageStatus.value = "loading";
    getProductList({
      current: productPage.current,
      size: productPage.size,
      productName: productQuery.productName,
      model: productQuery.model,
    })
      .then(res => {
        const records = res?.data?.records || res?.records || res?.data || [];
        const total = res?.data?.total || res?.total || 0;
        const next = Array.isArray(records) ? records : [];
        productList.value =
          productPage.current === 1 ? next : [...productList.value, ...next];
        productPage.total = Number(total || productList.value.length);
        if (productList.value.length >= productPage.total) {
          productPageStatus.value = "nomore";
        } else {
          productPageStatus.value = "loadmore";
          productPage.current++;
        }
      })
      .catch(() => {
        productPageStatus.value = "loadmore";
      });
  };
  const selectProduct = row => {
    bomForm.productModelId = row.id;
    bomForm.productName = row.productName || "";
    bomForm.productModelName = row.model || "";
    showProductPicker.value = false;
  };
  onReachBottom(() => {
    getList();
  });
@@ -176,4 +465,78 @@
    margin: 0 !important;
    margin-bottom: 15rpx !important;
  }
  .popup-container {
    background: #fff;
    border-radius: 20rpx 20rpx 0 0;
    max-height: 80vh;
    display: flex;
    flex-direction: column;
  }
  .popup-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 24rpx 28rpx;
    border-bottom: 1rpx solid #f0f0f0;
  }
  .popup-title {
    font-size: 30rpx;
    font-weight: 600;
    color: #333;
  }
  .popup-cancel {
    font-size: 28rpx;
    color: #666;
  }
  .popup-confirm {
    font-size: 28rpx;
    color: #006cfb;
    font-weight: 600;
  }
  .popup-body {
    padding: 20rpx 24rpx 30rpx;
    overflow: hidden;
    flex: 1;
  }
  .picker-search {
    display: flex;
    gap: 16rpx;
    margin-bottom: 16rpx;
  }
  .picker-list {
    height: 60vh;
  }
  .picker-item {
    padding: 22rpx 0;
    border-bottom: 1rpx solid #f5f5f5;
  }
  .picker-item__title {
    font-size: 28rpx;
    color: #333;
    font-weight: 600;
  }
  .picker-item__sub {
    margin-top: 6rpx;
    font-size: 24rpx;
    color: #666;
    display: flex;
    justify-content: space-between;
    gap: 16rpx;
  }
  .picker-item__unit {
    color: #999;
    white-space: nowrap;
  }
</style>