Переглянути джерело

feat(product): 统一品牌搜索功能并优化价格格式化

- 在商品基础页面移除商品编号必填验证
- 添加表格行价格格式化功能,统一保留两位小数
- 将所有品牌搜索组件统一为支持远程搜索的选择器
- 更新品牌查询参数从brandName改为brandId保持一致性
- 修复分类页面平台选项值映射错误
- 优化品牌搜索性能,添加防抖和分页加载
- 添加缺失的品牌类型定义和API导入
肖路 1 місяць тому
батько
коміт
7c520ff798

+ 5 - 0
src/api/product/poolLink/types.ts

@@ -242,6 +242,11 @@ export interface PoolLinkQuery extends PageQuery {
    */
   brandId?: string | number;
 
+  /**
+   * 品牌名称
+   */
+  brandName?: string;
+
   /**
    * 分类ID
    */

+ 4 - 0
src/api/product/products/types.ts

@@ -170,6 +170,10 @@ export interface ProductsQuery extends PageQuery {
    * 商品品牌id
    */
   brandId?: string | number;
+  /**
+   * 商品品牌名称
+   */
+  brandName?: string;
   /**
    * 商品状态 0-下架 1-上架
    */

+ 17 - 6
src/views/product/base/add.vue

@@ -151,7 +151,7 @@
             <!-- 商品编号 -->
             <el-row :gutter="20">
               <el-col :span="12">
-                <el-form-item label="商品编号:" prop="productNo" required>
+                <el-form-item label="商品编号:" prop="productNo" >
                   <el-input
                     v-model="productForm.productNo"
                     placeholder="002169745"
@@ -742,7 +742,7 @@
                   </el-table-column>
                   <el-table-column label="起订价格" width="150">
                     <template #default="{ row }">
-                      <el-input v-model="row.minOrderPrice" placeholder="请输入" />
+                      <el-input v-model="row.minOrderPrice" placeholder="请输入" @blur="formatRowPrice(row, 'minOrderPrice')" />
                     </template>
                   </el-table-column>
                   <el-table-column label="打样工期[天]" width="150">
@@ -1046,7 +1046,7 @@ const productForm = reactive<BaseForm>({
 
 // 表单验证规则
 const productRules = {
-  productNo: [{ required: true, message: '商品编号不能为空', trigger: 'blur' }],
+  // productNo: [{ required: true, message: '商品编号不能为空', trigger: 'blur' }],
   itemName: [{ required: true, message: '商品名称不能为空', trigger: 'blur' }],
   brandId: [{ required: true, message: '商品品牌不能为空', trigger: 'change' }],
   mainLibraryIntro: [{ required: true, message: '主供应商不能为空', trigger: 'change' }],
@@ -1210,7 +1210,7 @@ const nextStep = async () => {
     productForm.topCategoryId = categoryForm.topCategoryId;
     productForm.mediumCategoryId = categoryForm.mediumCategoryId;
     productForm.bottomCategoryId = categoryForm.bottomCategoryId;
-    
+
     currentStep.value++;
   } else if (currentStep.value === 1) {
     // 验证商品信息表单并提交
@@ -1330,7 +1330,18 @@ const formatPrice = (field: string) => {
   if (val !== undefined && val !== null && val !== '') {
     const num = parseFloat(String(val));
     if (!isNaN(num)) {
-      (productForm as any)[field] = parseFloat(num.toFixed(2));
+      (productForm as any)[field] = num.toFixed(2);
+    }
+  }
+};
+
+// 格式化表格行中的价格为两位小数
+const formatRowPrice = (row: any, field: string) => {
+  const val = row[field];
+  if (val !== undefined && val !== null && val !== '') {
+    const num = parseFloat(String(val));
+    if (!isNaN(num)) {
+      row[field] = num.toFixed(2);
     }
   }
 };
@@ -1691,7 +1702,7 @@ onMounted(async () => {
 
     .image-preview {
       position: relative;
-      
+
       .preview-image {
         width: 178px;
         height: 178px;

+ 4 - 5
src/views/product/base/index.vue

@@ -16,16 +16,15 @@
                 </el-form-item>
               </el-col>
               <el-col :span="6">
-                <el-form-item label="商品品牌" prop="brandName">
+                <el-form-item label="商品品牌" prop="brandId">
                   <el-select
-                    v-model="queryParams.brandName"
+                    v-model="queryParams.brandId"
                     placeholder="请输入品牌名称搜索"
                     filterable
                     remote
                     clearable
                     :remote-method="handleBrandSearch"
                     :loading="brandLoading"
-                    value-key="brandName"
                     style="width: 100%"
                     @keyup.enter="handleQuery"
                   >
@@ -33,7 +32,7 @@
                       v-for="item in brandOptions"
                       :key="item.id"
                       :label="item.brandName"
-                      :value="item.brandName"
+                      :value="item.id"
                     />
                   </el-select>
                 </el-form-item>
@@ -328,7 +327,7 @@ const data = reactive<PageData<BaseForm, BaseQuery>>({
     pageSize: 10,
     productNo: undefined,
     itemName: undefined,
-    brandName: undefined,
+    brandId: undefined,
     productTag: undefined,
     purchaseNature: undefined,
     supplierType: undefined,

+ 34 - 28
src/views/product/base/shelfReview.vue

@@ -30,16 +30,24 @@
                 </el-form-item>
               </el-col>
               <el-col :span="6">
-                <el-form-item label="商品品牌" prop="brandName">
-                  <el-select-v2
-                    v-model="queryParams.brandName"
-                    :options="brandOptionsFormatted"
-                    placeholder="请选择商品品牌"
-                    clearable
+                <el-form-item label="商品品牌" prop="brandId">
+                  <el-select
+                    v-model="queryParams.brandId"
+                    placeholder="请输入品牌名称搜索"
                     filterable
+                    remote
+                    clearable
+                    :remote-method="handleBrandSearch"
                     :loading="brandLoading"
-                    @visible-change="handleBrandVisibleChange"
-                  />
+                    style="width: 100%"
+                  >
+                    <el-option
+                      v-for="item in brandOptions"
+                      :key="item.id"
+                      :label="item.brandName"
+                      :value="item.id"
+                    />
+                  </el-select>
                 </el-form-item>
               </el-col>
 
@@ -220,6 +228,7 @@
 import { listBase, getBase, shelfReview, brandList, categoryTree } from '@/api/product/base';
 import { BaseVO, BaseQuery, BaseForm } from '@/api/product/base/types';
 import { BrandVO } from '@/api/product/brand/types';
+import { listBrand } from '@/api/product/brand';
 import { categoryTreeVO } from '@/api/product/category/types';
 import { useRouter, useRoute } from 'vue-router';
 
@@ -236,12 +245,7 @@ const multiple = ref(true);
 const total = ref(0);
 const brandOptions = ref<BrandVO[]>([]);
 const brandLoading = ref(false);
-const brandOptionsFormatted = computed(() => {
-  return brandOptions.value.slice(0, 500).map((item) => ({
-    label: item.brandName,
-    value: item.brandName // review.vue使用brandName作为value
-  }));
-});
+let brandSearchTimer: ReturnType<typeof setTimeout> | null = null;
 const categoryOptions = ref<categoryTreeVO[]>([]);
 const hasMore = ref(true); // 是否还有更多数据
 const pageHistory = ref([]);
@@ -286,7 +290,7 @@ const queryParams = ref<BaseQuery>({
   way: undefined,
   productNo: undefined,
   itemName: undefined,
-  brandName: undefined,
+  brandId: undefined,
   purchaseNature: undefined,
   bottomCategoryId: undefined,
   isSelf: undefined,
@@ -366,8 +370,8 @@ const initRouteParams = () => {
   if (route.query.productStatus) {
     queryParams.value.productStatus = Number(route.query.productStatus);
   }
-  if (route.query.brandName) {
-    queryParams.value.brandName = route.query.brandName as string;
+  if (route.query.brandId) {
+    queryParams.value.brandId = route.query.brandId as string;
   }
   if (route.query.bottomCategoryId) {
     queryParams.value.bottomCategoryId = route.query.bottomCategoryId as string;
@@ -457,24 +461,25 @@ const submitReview = async () => {
   await getList();
 };
 
-/** 查询品牌列表(实时请求,每次只加载500条) */
-const getBrandList = async () => {
+/** 加载品牌选项(默认100条) */
+const loadBrandOptions = async (keyword?: string) => {
+  brandLoading.value = true;
   try {
-    brandLoading.value = true;
-    const res = await brandList({ pageNum: 1, pageSize: 500 });
-    brandOptions.value = res.data || [];
+    const res = await listBrand({ pageNum: 1, pageSize: 100, brandName: keyword });
+    brandOptions.value = res.rows || [];
   } catch (error) {
-    console.error('获取品牌列表失败:', error);
+    console.error('加载品牌列表失败:', error);
   } finally {
     brandLoading.value = false;
   }
 };
 
-/** 处理品牌下拉框显示/隐藏 */
-const handleBrandVisibleChange = (visible: boolean) => {
-  if (visible && brandOptions.value.length === 0) {
-    getBrandList();
-  }
+/** 品牌远程搜索(防抖) */
+const handleBrandSearch = (query: string) => {
+  if (brandSearchTimer) clearTimeout(brandSearchTimer);
+  brandSearchTimer = setTimeout(() => {
+    loadBrandOptions(query || undefined);
+  }, 300);
 };
 
 /** 查询分类树 */
@@ -486,6 +491,7 @@ const getCategoryTree = async () => {
 onMounted(() => {
   getCategoryTree();
   initRouteParams();
+  loadBrandOptions();
   getList();
 });
 </script>

+ 2 - 2
src/views/product/category/index.vue

@@ -126,8 +126,8 @@
           <el-col :span="12">
             <el-form-item label="平台" prop="platform">
               <el-select v-model="form.platform" placeholder="请选择所属平台" style="width: 100%">
-                <el-option label="工业品" :value="0" />
-                <el-option label="PC端" :value="1" />
+                <el-option label="PC端" :value="0" />
+                <el-option label="工业品" :value="1" />
               </el-select>
             </el-form-item>
           </el-col>

+ 56 - 15
src/views/product/pool/reviewDetail.vue

@@ -24,7 +24,24 @@
               </el-col>
               <el-col :span="6">
                 <el-form-item label="商品品牌" prop="brandId">
-                  <el-input v-model="queryParams.brandId" placeholder="请选择" clearable />
+                  <el-select
+                    v-model="queryParams.brandId"
+                    placeholder="请输入品牌名称搜索"
+                    filterable
+                    remote
+                    clearable
+                    :remote-method="handleBrandSearch"
+                    :loading="brandLoading"
+                    style="width: 100%"
+                    @keyup.enter="handleQuery"
+                  >
+                    <el-option
+                      v-for="item in brandOptions"
+                      :key="item.id"
+                      :label="item.brandName"
+                      :value="item.id"
+                    />
+                  </el-select>
                 </el-form-item>
               </el-col>
               <el-col :span="6">
@@ -43,23 +60,19 @@
                   <el-tree-select
                     v-model="queryParams.categoryId"
                     :data="categoryOptions"
-                    :props="{ value: 'id', label: 'label', children: 'children' }"
-                    check-strictly
-                    :render-after-expand="false"
-                    clearable
+                    :props="{ value: 'id', label: 'label', children: 'children' } as any"
+                    value-key="id"
                     placeholder="请选择商品类别"
-                  >
-                    <template #default="{ data }">
-                      <span>{{ getCategoryFullPath(data.id) }}</span>
-                    </template>
-                  </el-tree-select>
-                </el-form-item>
-              </el-col>
-              <el-col :span="6">
-                <el-form-item label="创建供应商" prop="supplier">
-                  <el-input v-model="queryParams.supplier" placeholder="请选择创建供应商" clearable />
+                    clearable
+                    check-strictly
+                  />
                 </el-form-item>
               </el-col>
+<!--              <el-col :span="6">-->
+<!--                <el-form-item label="创建供应商" prop="supplier">-->
+<!--                  <el-input v-model="queryParams.supplier" placeholder="请选择创建供应商" clearable />-->
+<!--                </el-form-item>-->
+<!--              </el-col>-->
               <el-col :span="6">
                 <el-form-item label="入池时间" prop="dateRange">
                   <el-date-picker
@@ -372,6 +385,8 @@ import { categoryTree, listBase } from '@/api/product/base';
 import { BaseVO, BaseQuery } from '@/api/product/base/types';
 import { listPoolLink, batchAddProducts, BatchAddProductData, batchReview, reSubmit, editPrice, editStock, PoolLinkForm } from '@/api/product/poolLink';
 import { PoolLinkQuery, PoolLinkVO } from '@/api/product/poolLink/types';
+import { listBrand } from '@/api/product/brand';
+import { BrandVO } from '@/api/product/brand/types';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const router = useRouter();
@@ -407,6 +422,10 @@ const queryParams = ref({
 const categoryOptions = ref<any[]>([]);
 const categoryMap = ref<Map<string | number, any>>(new Map());
 
+const brandOptions = ref<BrandVO[]>([]);
+const brandLoading = ref(false);
+let brandSearchTimer: ReturnType<typeof setTimeout> | null = null;
+
 // 添加商品对话框
 const addProductDialog = reactive({
   visible: false,
@@ -429,6 +448,27 @@ const addProductQuery = ref({
 const selectedProducts = ref<BaseVO[]>([]);
 const addProductTableRef = ref<any>();
 
+/** 加载品牌选项(默认100条) */
+const loadBrandOptions = async (keyword?: string) => {
+  brandLoading.value = true;
+  try {
+    const res = await listBrand({ pageNum: 1, pageSize: 100, brandName: keyword });
+    brandOptions.value = res.rows || [];
+  } catch (error) {
+    console.error('加载品牌列表失败:', error);
+  } finally {
+    brandLoading.value = false;
+  }
+};
+
+/** 品牌远程搜索(防抖) */
+const handleBrandSearch = (query: string) => {
+  if (brandSearchTimer) clearTimeout(brandSearchTimer);
+  brandSearchTimer = setTimeout(() => {
+    loadBrandOptions(query || undefined);
+  }, 300);
+};
+
 /** 获取分类树 */
 const getCategoryTree = async () => {
   try {
@@ -834,6 +874,7 @@ const submitStockForm = async () => {
 
 onMounted(() => {
   getCategoryTree();
+  loadBrandOptions();
   getList();
 });
 </script>

+ 5 - 5
src/views/product/protocolInfo/productManage.vue

@@ -25,9 +25,9 @@
             <el-form-item label="产品名称" prop="productName">
               <el-input v-model="queryParams.productName" placeholder="请输入产品名称" clearable @keyup.enter="handleQuery" />
             </el-form-item>
-            <el-form-item label="商品品牌" prop="brandId">
-              <el-select v-model="queryParams.brandId" placeholder="请选择" clearable style="width: 200px">
-                <el-option v-for="item in brandList" :key="item.id" :label="item.brandName" :value="item.id" />
+            <el-form-item label="商品品牌" prop="brandName">
+              <el-select v-model="queryParams.brandName" placeholder="请选择" clearable style="width: 200px">
+                <el-option v-for="item in brandList" :key="item.id" :label="item.brandName" :value="item.brandName" />
               </el-select>
             </el-form-item>
             <el-form-item label="商品状态" prop="productStatus">
@@ -277,7 +277,7 @@ const queryParams = ref<ProductsQuery>({
   protocolNo: undefined,
   productNo: undefined,
   productName: undefined,
-  brandId: undefined,
+  brandName: undefined,
   productStatus: undefined
 });
 
@@ -402,7 +402,7 @@ const resetQuery = () => {
     protocolNo: pNo,
     productNo: undefined,
     productName: undefined,
-    brandId: undefined,
+    brandName: undefined,
     productStatus: undefined
   };
   handleQuery();