From 4c8d18cc5ed8a7b0e220c91a858d16d0310896df Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期一, 29 六月 2026 16:50:03 +0800
Subject: [PATCH] BOM新增修改删除功能开发、以及BOM结构的可编辑

---
 src/pages/productionDesign/bom/index.vue |  365 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 364 insertions(+), 1 deletions(-)

diff --git a/src/pages/productionDesign/bom/index.vue b/src/pages/productionDesign/bom/index.vue
index 97ba5f6..c3368d8 100644
--- a/src/pages/productionDesign/bom/index.vue
+++ b/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: "纭鍒犻櫎璇OM锛�",
+      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>

--
Gitblit v1.9.3