Selaa lähdekoodia

feat(product): 添加商品描述字段并优化表单验证

- 新增商品描述字段到产品基础添加页面
- 将税率下拉改为只读显示,由税率编码自动带出
- 添加商品重量和体积输入限制,只允许数字和小数点
- 优化价格验证逻辑,支持完整的采购价格链条校验
- 添加参考链接输入过滤,移除中文字符和全角空格
- 更新价格关系提示,明确市场价格层级要求
- 移除移动端详情编辑功能,简化商品详情管理
- 优化轮播图上传组件,统一使用image-upload组件
- 添加关键输入框引用,便于验证失败时自动聚焦
- 实现三级分类联动填充采购信息功能
肖路 4 päivää sitten
vanhempi
sitoutus
a647eb2178

+ 1 - 1
src/api/external/product/types.ts

@@ -420,7 +420,7 @@ export interface ThirdProductVO {
   unitName?: string;
 
   /**
-   * 可用库存数
+   * 现有库存数
    */
   availableStock?: number;
 

+ 40 - 0
src/api/product/base/types.ts

@@ -173,6 +173,26 @@ export interface BaseVO {
    */
   purchasingPersonnel?: string;
 
+  /**
+   * 采购编号(来源于三级分类)
+   */
+  purchaseNo?: string;
+
+  /**
+   * 采购名称(来源于三级分类)
+   */
+  purchaseName?: string;
+
+  /**
+   * 采购负责人编号(来源于三级分类)
+   */
+  purchaseManagerNo?: string;
+
+  /**
+   * 采购负责人姓名(来源于三级分类)
+   */
+  purchaseManagerName?: string;
+
   /**
    * 旧属性类型
    */
@@ -494,6 +514,26 @@ export interface BaseForm extends BaseEntity {
    */
   purchasingPersonnel?: string;
 
+  /**
+   * 采购编号(来源于三级分类)
+   */
+  purchaseNo?: string;
+
+  /**
+   * 采购名称(来源于三级分类)
+   */
+  purchaseName?: string;
+
+  /**
+   * 采购负责人编号(来源于三级分类)
+   */
+  purchaseManagerNo?: string;
+
+  /**
+   * 采购负责人姓名(来源于三级分类)
+   */
+  purchaseManagerName?: string;
+
   /**
    * 旧属性类型
    */

+ 14 - 0
src/api/product/baseAudit/index.ts

@@ -1,6 +1,7 @@
 import request from '@/utils/request';
 import { AxiosPromise } from 'axios';
 import { BaseAuditVO, BaseAuditForm, BaseAuditQuery, ProductAuditForm } from '@/api/product/baseAudit/types';
+import { BaseVO, BaseForm, BaseQuery, StatusCountVo } from '@/api/product/base/types';
 
 /**
  * 查询商品审核列表
@@ -85,3 +86,16 @@ export const commitBaseAudit = (data: BaseAuditForm) => {
     data: data
   });
 };
+
+/**
+ * 获取审核池出池的商品列表
+ * @param query
+ * @returns {*}
+ */
+export const getPoolAuditProducts = (query?: any): AxiosPromise<BaseVO[]> => {
+  return request({
+    url: '/product/poolAudit/getPoolAuditProducts',
+    method: 'get',
+    params: query
+  });
+};

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

@@ -166,6 +166,11 @@ export interface PptTemplateForm extends BaseEntity {
 
 export interface PptTemplateQuery extends PageQuery {
 
+  /**
+   * 是否我的模板
+   */
+  myTemplate?: string;
+
   /**
    * 模板名称(显示在列表)
    */

+ 3 - 3
src/api/product/priceInventory/types.ts

@@ -35,7 +35,7 @@ export interface PriceInventoryVO {
   totalInventory: number;
 
   /**
-   * 当前可用库存
+   * 当前现有库存
    */
   nowInventory: number;
 
@@ -103,7 +103,7 @@ export interface PriceInventoryForm extends BaseEntity {
   totalInventory?: number;
 
   /**
-   * 当前可用库存
+   * 当前现有库存
    */
   nowInventory?: number;
 
@@ -167,7 +167,7 @@ export interface PriceInventoryQuery extends PageQuery {
   totalInventory?: number;
 
   /**
-   * 当前可用库存
+   * 当前现有库存
    */
   nowInventory?: number;
 

+ 15 - 0
src/api/product/protocolInfo/types.ts

@@ -64,16 +64,31 @@ export interface InfoVO {
    */
   salesmanId: string | number;
 
+  /**
+   * 业务员名称
+   */
+  salesmanName: string;
+
   /**
    * 公司id
    */
   companyId: string | number;
 
+  /**
+   * 公司名称
+   */
+  companyName: string;
+
   /**
    * 客服id
    */
   serviceId: string | number;
 
+  /**
+   * 客服名称
+   */
+  serviceName: string;
+
   /**
    * 数据来源
    */

+ 5 - 0
src/api/system/taxCode/types.ts

@@ -54,6 +54,11 @@ export interface TaxCodeVO {
    */
   isBottom: string;
 
+  /**
+   * 税率
+   */
+  taxrate?: number | string;
+
 }
 
 export interface TaxCodeForm extends BaseEntity {

+ 18 - 3
src/components/TaxCodeSelect/index.vue

@@ -68,9 +68,16 @@
                 <el-link :type="isRowDisabled(row) ? 'info' : 'primary'" :disabled="isRowDisabled(row)" :underline="false">{{ row.abbreviation }}</el-link>
               </template>
             </el-table-column>
-            <!-- <el-table-column label="说明" align="center" prop="remark" min-width="150" show-overflow-tooltip />
-            <el-table-column label="税率" align="center" prop="taxRate" width="80" />
-            <el-table-column label="用户选择比例" align="center" prop="selectRatio" width="110" /> -->
+            <el-table-column label="说明" align="center" prop="remark" min-width="150" show-overflow-tooltip>
+              <template #default="{ row }">
+                <el-link :type="isRowDisabled(row) ? 'info' : 'primary'" :disabled="isRowDisabled(row)" :underline="false">{{ row.remark }}</el-link>
+              </template>
+            </el-table-column>
+            <el-table-column label="税率" align="center" prop="explain" width="90">
+              <template #default="{ row }">
+                <el-link :type="isRowDisabled(row) ? 'info' : 'primary'" :disabled="isRowDisabled(row)" :underline="false">{{ formatTaxRate(row.taxrate) }}</el-link>
+              </template>
+            </el-table-column>
           </el-table>
 
           <!-- 分页 -->
@@ -203,6 +210,14 @@ const handleSearch = () => {
   getList();
 };
 
+/** 格式化税率为百分制显示 */
+const formatTaxRate = (val: any): string => {
+  if (val === null || val === undefined || val === '') return '';
+  const num = Number(val);
+  if (isNaN(num)) return String(val);
+  return `${Math.round(num * 100)}%`;
+};
+
 /** 判断行是否禁用:非叶子节点(isBottom === 0)不可选 */
 const isRowDisabled = (row: TaxCodeVO): boolean => {
   return Number((row as any)?.isBottom) === 0;

+ 1 - 1
src/views/external/product/index.vue

@@ -55,7 +55,7 @@
         <el-table-column label="供应价" align="center" prop="purchasePrice" width="100" />
         <el-table-column label="第三方售价" align="center" prop="externalPrice" width="100" />
         <el-table-column label="限定库存" align="center" prop="availableStock" width="100" />
-        <el-table-column label="可用库存" align="center" prop="availableStock" width="100" />
+        <el-table-column label="现有库存" align="center" prop="availableStock" width="100" />
         <el-table-column label="总订单" align="center" width="100">
           <template #default="scope">
             <span>0</span>

+ 271 - 76
src/views/product/base/add.vue

@@ -188,6 +188,19 @@
               <div class="form-item-tip">A10产品名称由系统自动拼接:品牌名 + 规格型号 + 产品分类(三级分类)+ 发票规格,无需手动填写</div>
             </el-form-item>
 
+            
+            <!-- 商品描述 -->
+            <el-form-item label="商品描述:">
+              <el-input
+                v-model="productForm.productDescription"
+                type="textarea"
+                :rows="3"
+                placeholder="请输入商品描述"
+                maxlength="500"
+                show-word-limit
+              />
+            </el-form-item>
+
             <!-- 规格型号 和 UPC(69)条码 -->
             <el-row :gutter="20">
               <el-col :span="12">
@@ -253,7 +266,7 @@
             <!-- 税率编码 、税率 和 币种 -->
             <el-row :gutter="20">
               <el-col :span="12">
-                <el-form-item label="税率编码:">
+                <el-form-item label="税率编码:" required>
                   <el-input
                     v-model="taxCodeNo"
                     placeholder="点击选择税率编码"
@@ -270,9 +283,7 @@
               </el-col>
               <el-col :span="12">
                 <el-form-item label="税率:" required>
-                  <el-select v-model="productForm.taxRate" placeholder="请选择税率" clearable class="w-full">
-                    <el-option v-for="option in taxRateOptions" :key="option.id" :label="`${option.taxrateNo},${option.taxrateName}`" :value="option.taxrate" />
-                  </el-select>
+                  <el-input :model-value="formatTaxRateDisplay(productForm.taxRate)" placeholder="由税率编码带出" readonly disabled class="w-full" />
                 </el-form-item>
               </el-col>
             </el-row>
@@ -312,13 +323,14 @@
               <el-input v-model="productForm.promotionTitle" type="textarea" :rows="3" placeholder="请输入促销标题" maxlength="300" show-word-limit />
             </el-form-item>
 
-            <!-- 商品简介 -->
-            <el-form-item label="商品简介:">
+
+                        <!-- 商品说明 -->
+            <el-form-item label="商品说明:">
               <el-input
-                v-model="productForm.productDescription"
+                v-model="productForm.productExplain"
                 type="textarea"
                 :rows="3"
-                placeholder="请输入商品简介"
+                placeholder="请输入商品说明"
                 maxlength="500"
                 show-word-limit
               />
@@ -328,7 +340,13 @@
             <el-row :gutter="20">
               <el-col :span="12">
                 <el-form-item label="商品重量:">
-                  <el-input v-model="productForm.productWeight" placeholder="0" maxlength="10" show-word-limit>
+                  <el-input
+                    v-model="productForm.productWeight"
+                    placeholder="0"
+                    maxlength="10"
+                    show-word-limit
+                    @input="handleWeightInput"
+                  >
                     <template #append>
                       <el-select v-model="productForm.weightUnit" placeholder="请选择" style="width: 100px">
                         <el-option label="kg" value="kg" />
@@ -341,7 +359,13 @@
               </el-col>
               <el-col :span="12">
                 <el-form-item label="商品体积:">
-                  <el-input v-model="productForm.productVolume" placeholder="0" maxlength="10" show-word-limit>
+                  <el-input
+                    v-model="productForm.productVolume"
+                    placeholder="0"
+                    maxlength="10"
+                    show-word-limit
+                    @input="handleVolumeInput"
+                  >
                     <template #append>
                       <el-select v-model="productForm.volumeUnit" placeholder="请选择" style="width: 80px">
                         <el-option label="m³" value="m3" />
@@ -355,8 +379,15 @@
             </el-row>
 
             <!-- 参考链接 -->
-            <el-form-item label="参考链接">
-              <el-input v-model="productForm.referenceLink" type="textarea" :rows="3" placeholder="请输入参考链接" />
+            <el-form-item label="参考链接" prop="referenceLink" required>
+              <el-input
+                ref="referenceLinkRef"
+                v-model="productForm.referenceLink"
+                type="textarea"
+                :rows="3"
+                placeholder="请输入参考链接"
+                @input="handleReferenceLinkInput"
+              />
             </el-form-item>
 
             <!-- 主供应商 -->
@@ -404,12 +435,20 @@
             <el-row :gutter="20">
               <el-col :span="8">
                 <el-form-item label="市场价:" prop="marketPrice" required>
-                  <el-input v-model="productForm.marketPrice" type="number" placeholder="请输入市场价" :min="0" @blur="formatPrice('marketPrice')" />
+                  <el-input
+                    ref="marketPriceRef"
+                    v-model="productForm.marketPrice"
+                    type="number"
+                    placeholder="请输入市场价"
+                    :min="0"
+                    @blur="formatPrice('marketPrice')"
+                  />
                 </el-form-item>
               </el-col>
               <el-col :span="8">
                 <el-form-item label="官网价:" prop="memberPrice" required>
                   <el-input
+                    ref="memberPriceRef"
                     v-model="productForm.memberPrice"
                     type="number"
                     placeholder="请输入平台售价"
@@ -421,6 +460,7 @@
               <el-col :span="8">
                 <el-form-item label="最低售价:" prop="minSellingPrice" required>
                   <el-input
+                    ref="minSellingPriceRef"
                     v-model="productForm.minSellingPrice"
                     type="number"
                     placeholder="请输入最低售价"
@@ -438,7 +478,7 @@
               </el-col>
               <el-col :span="8">
                 <el-form-item label="备注:">
-                  <span class="currency-text">市场价>官网价>最低售价</span>
+                  <span class="currency-text">市场价&gt;官网价&ge;最低售价&ge;最高采购价&ge;采购价</span>
                 </el-form-item>
               </el-col>
             </el-row>
@@ -456,6 +496,7 @@
               <el-col :span="12">
                 <el-form-item label="采购价:" prop="purchasingPrice" required>
                   <el-input
+                    ref="purchasingPriceRef"
                     v-model="productForm.purchasingPrice"
                     type="number"
                     placeholder="请输入采购价"
@@ -465,8 +506,9 @@
                 </el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item label="最高采购价:">
+                <el-form-item label="最高采购价:" prop="maxPurchasePrice" required>
                   <el-input
+                    ref="maxPurchasePriceRef"
                     v-model="productForm.maxPurchasePrice"
                     type="number"
                     placeholder="请输入最高采购价"
@@ -612,14 +654,14 @@
           <el-form ref="detailFormRef" :model="productForm" label-width="120px" class="product-info-form">
             <!-- 商品主图 -->
             <el-form-item label="商品主图:" required>
-              <upload-image v-model="productForm.productImage" :limit="1" width="178px" height="178px" imageText="" />
-              <div class="form-item-tip">从图片库选择,建议尺寸300*300px</div>
+              <image-upload v-model="productForm.productImage" :limit="1" />
+              <div class="form-item-tip">建议尺寸300*300px</div>
             </el-form-item>
 
             <!-- 商品轮播图 -->
             <el-form-item label="商品轮播图:" required>
-              <upload-image v-model="carouselImages" :limit="20" width="120px" height="120px" imageText="" />
-              <div class="form-item-tip">从图片库选择,支持多选,建议尺寸300*300px</div>
+              <image-upload v-model="carouselImages" :limit="20" />
+              <div class="form-item-tip">支持多张上传,建议尺寸300*300px</div>
             </el-form-item>
 
             <!-- 商品详情 -->
@@ -628,9 +670,6 @@
                 <el-tab-pane label="电脑端详情" name="pc">
                   <Editor v-model="productForm.pcDetail" :height="400" />
                 </el-tab-pane>
-                <el-tab-pane label="移动端详情" name="mobile">
-                  <Editor v-model="productForm.mobileDetail" :height="400" />
-                </el-tab-pane>
               </el-tabs>
             </el-form-item>
           </el-form>
@@ -767,9 +806,9 @@ import { useRoute, useRouter } from 'vue-router';
 import { ElMessage } from 'element-plus';
 import { Warning, ArrowRight, Check, Plus, CircleCheck, Search } from '@element-plus/icons-vue';
 import Editor from '@/components/Editor/index.vue';
-import UploadImage from '@/components/upload-image/index.vue';
 import TaxCodeSelect from '@/components/TaxCodeSelect/index.vue';
 import { categoryTreeVO, CategoryVO } from '@/api/product/category/types';
+import { getCategory } from '@/api/product/category';
 import { BrandVO } from '@/api/product/brand/types';
 import { BaseForm } from '@/api/product/base/types';
 import { ClassificationDiyForm } from '@/api/product/classificationDiy/types';
@@ -783,8 +822,7 @@ import {
   categoryAttributeList,
   getAfterSaleList,
   getServiceList,
-  getUnitList,
-  getTaxRateList
+  getUnitList
 } from '@/api/product/base';
 import { addBaseAudit, updateBaseAudit, getBaseAudit } from '@/api/product/baseAudit';
 import { BaseAuditVO, BaseAuditQuery, BaseAuditForm } from '@/api/product/baseAudit/types';
@@ -803,6 +841,14 @@ const loading = ref(false);
 const submitLoading = ref(false);
 const productFormRef = ref();
 
+// 关键输入框 refs,用于校验失败时聚焦
+const referenceLinkRef = ref();
+const marketPriceRef = ref();
+const memberPriceRef = ref();
+const minSellingPriceRef = ref();
+const maxPurchasePriceRef = ref();
+const purchasingPriceRef = ref();
+
 // 服务保障和安装服务的多选框
 const serviceGuarantees = ref<(string | number)[]>([]);
 const installationServices = ref<string[]>([]);
@@ -810,24 +856,37 @@ const installationServices = ref<string[]>([]);
 // 商品详情选项卡
 const activeDetailTab = ref('pc');
 
-// 轮播图URL数组(UI管理用)
-const carouselImages = ref<string[]>([]);
-
-// 税率选项
-const taxRateOptions = ref<any[]>([]);
+// 轮播图(逗号分隔的 ossId 字符串,由 ImageUpload 管理)
+const carouselImages = ref<string>('');
 
 // 税率编码选择组件
 const taxCodeSelectRef = ref();
 // 已选的税率编码(显示用)
 const taxCodeNo = ref('');
 
+// 格式化税率显示(小数转百分比)
+const formatTaxRateDisplay = (val: any): string => {
+  if (val === null || val === undefined || val === '') return '';
+  const num = Number(val);
+  if (isNaN(num)) return String(val);
+  return `${Math.round(num * 100)}%`;
+};
+
 // 处理税率编码选择
 const handleTaxCodeSelect = async (row: any) => {
   (productForm as any).taxationId = row.id;
+  // 选择税率编码时自动带出税率
+  if (row.taxrate !== undefined && row.taxrate !== null) {
+    productForm.taxRate = Number(row.taxrate);
+  }
   try {
     const taxRes = await getTaxCode(row.id);
     if (taxRes.data) {
       taxCodeNo.value = `${taxRes.data.taxationNo},${taxRes.data.name}`;
+      // 使用详情接口返回的税率值(更准确)
+      if (taxRes.data.taxrate !== undefined && taxRes.data.taxrate !== null) {
+        productForm.taxRate = Number(taxRes.data.taxrate);
+      }
     } else {
       taxCodeNo.value = row.taxationNo ? `${row.taxationNo},${row.name}` : (row.name || '');
     }
@@ -1013,8 +1072,12 @@ const productForm = reactive<BaseForm>({
   minSellingPrice: undefined,
   purchasingPrice: undefined,
   maxPurchasePrice: undefined,
-  productNature: '1',
-  purchasingPersonnel: '1',
+  productNature: '',
+  purchasingPersonnel: '',
+  purchaseNo: undefined,
+  purchaseName: undefined,
+  purchaseManagerNo: undefined,
+  purchaseManagerName: undefined,
   pcDetail: undefined,
   mobileDetail: undefined,
   taxRate: undefined,
@@ -1030,9 +1093,23 @@ const productRules = {
   brandId: [{ required: true, message: '商品品牌不能为空', trigger: 'change' }],
   supplierNo: [{ required: true, message: '主供应商不能为空', trigger: 'change' }],
   marketPrice: [{ required: true, message: '市场价不能为空', trigger: 'blur' }],
-  memberPrice: [{ required: true, message: '平台售价不能为空', trigger: 'blur' }],
+  memberPrice: [{ required: true, message: '官网价不能为空', trigger: 'blur' }],
   minSellingPrice: [{ required: true, message: '最低售价不能为空', trigger: 'blur' }],
   purchasingPrice: [{ required: true, message: '采购价不能为空', trigger: 'blur' }],
+  maxPurchasePrice: [{ required: true, message: '最高采购价不能为空', trigger: 'blur' }],
+  referenceLink: [
+    { required: true, message: '参考链接不能为空', trigger: 'blur' },
+    {
+      validator: (_rule: any, value: any, callback: any) => {
+        if (value && /\u3000/.test(String(value))) {
+          callback(new Error('参考链接不能包含中文空格'));
+        } else {
+          callback();
+        }
+      },
+      trigger: 'blur'
+    }
+  ],
   productNature: [{ required: true, message: '产品经理不能为空', trigger: 'change' }],
   purchasingPersonnel: [{ required: true, message: '采购人员不能为空', trigger: 'change' }],
   taxRate: [{ required: true, message: '税率不能为空', trigger: 'change' }],
@@ -1169,10 +1246,12 @@ const handleLevel3SearchSelect = async (categoryId: string | number) => {
   if (level3Node) {
     categoryForm.bottomCategoryId = level3Node.id;
     selectedLevel3Name.value = level3Node.label;
+    await fillPurchaseFromCategory(level3Node.id, selectedCategory);
     await loadCategoryAttributes(level3Node.id);
   } else {
     categoryForm.bottomCategoryId = selectedCategory.id;
     selectedLevel3Name.value = selectedCategory.categoryName;
+    await fillPurchaseFromCategory(selectedCategory.id, selectedCategory);
     await loadCategoryAttributes(selectedCategory.id);
   }
 
@@ -1222,10 +1301,46 @@ const selectLevel3 = async (item: categoryTreeVO) => {
   categoryForm.bottomCategoryId = item.id;
   selectedLevel3Name.value = item.label;
 
+  // 联动填充产品经理与采购人员(从三级分类详情获取)
+  await fillPurchaseFromCategory(item.id);
+
   // 加载该分类下的属性列表
   await loadCategoryAttributes(item.id);
 };
 
+// 从三级分类联动填充采购信息
+const fillPurchaseFromCategory = async (categoryId: string | number, category?: CategoryVO) => {
+  try {
+    let detail: CategoryVO | undefined = category;
+    if (!detail) {
+      const res = await getCategory(categoryId);
+      detail = res.data;
+    }
+    if (!detail) return;
+    productForm.purchaseNo = detail.purchaseNo || undefined;
+    productForm.purchaseName = detail.purchaseName || undefined;
+    productForm.purchaseManagerNo = detail.purchaseManagerNo || undefined;
+    productForm.purchaseManagerName = detail.purchaseManagerName || undefined;
+
+    // 联动产品经理下拉:通过 purchaseManagerNo 匹配 staffCode
+    if (detail.purchaseManagerNo) {
+      const matchedManager = staffOptions.value.find((s) => s.staffCode === detail!.purchaseManagerNo);
+      if (matchedManager) {
+        productForm.productNature = String(matchedManager.staffId);
+      }
+    }
+    // 联动采购人员下拉:通过 purchaseNo 匹配 staffCode
+    if (detail.purchaseNo) {
+      const matchedPurchase = staffOptions.value.find((s) => s.staffCode === detail!.purchaseNo);
+      if (matchedPurchase) {
+        productForm.purchasingPersonnel = String(matchedPurchase.staffId);
+      }
+    }
+  } catch (e) {
+    console.error('获取三级分类采购信息失败:', e);
+  }
+};
+
 // 获取分类路径
 const getCategoryPath = () => {
   const parts = [];
@@ -1303,7 +1418,7 @@ const handleSubmit = async () => {
       submitLoading.value = false;
       return;
     }
-    if (!carouselImages.value || carouselImages.value.length === 0) {
+    if (!carouselImages.value) {
       ElMessage.warning('请上传商品轮播图');
       submitLoading.value = false;
       return;
@@ -1314,21 +1429,88 @@ const handleSubmit = async () => {
       return;
     }
 
-    // 校验价格关系:市场价 > 官网价 > 最低售价
-    const midRange = parseFloat(String(productForm.marketPrice));
-    const standard = parseFloat(String(productForm.memberPrice));
-    const certificate = parseFloat(String(productForm.minSellingPrice));
-    if (!isNaN(midRange) && !isNaN(standard) && !isNaN(certificate)) {
-      if (!(midRange > standard)) {
-        ElMessage.warning('市场价必须大于官网价');
-        submitLoading.value = false;
-        return;
-      }
-      if (!(standard > certificate)) {
-        ElMessage.warning('官网价必须大于最低售价');
-        submitLoading.value = false;
-        return;
-      }
+    // 校验价格关系:市场价 > 官网价 ≥ 最低售价 ≥ 最高采购价 ≥ 采购价,且均不能为 0
+    const market = parseFloat(String(productForm.marketPrice ?? ''));
+    const member = parseFloat(String(productForm.memberPrice ?? ''));
+    const minSell = parseFloat(String(productForm.minSellingPrice ?? ''));
+    const maxPurch = parseFloat(String(productForm.maxPurchasePrice ?? ''));
+    const purch = parseFloat(String(productForm.purchasingPrice ?? ''));
+
+    const focusField = (refObj: any) => {
+      nextTick(() => {
+        refObj.value?.focus?.();
+      });
+    };
+
+    // 均必须大于 0
+    if (isNaN(market) || market <= 0) {
+      ElMessage.warning('市场价必须大于 0');
+      focusField(marketPriceRef);
+      submitLoading.value = false;
+      return;
+    }
+    if (isNaN(member) || member <= 0) {
+      ElMessage.warning('官网价必须大于 0');
+      focusField(memberPriceRef);
+      submitLoading.value = false;
+      return;
+    }
+    if (isNaN(minSell) || minSell <= 0) {
+      ElMessage.warning('最低售价必须大于 0');
+      focusField(minSellingPriceRef);
+      submitLoading.value = false;
+      return;
+    }
+    if (isNaN(maxPurch) || maxPurch <= 0) {
+      ElMessage.warning('最高采购价必须大于 0');
+      focusField(maxPurchasePriceRef);
+      submitLoading.value = false;
+      return;
+    }
+    if (isNaN(purch) || purch <= 0) {
+      ElMessage.warning('采购价必须大于 0');
+      focusField(purchasingPriceRef);
+      submitLoading.value = false;
+      return;
+    }
+
+    // 链式大小校验
+    if (!(market > member)) {
+      ElMessage.warning('市场价必须大于官网价');
+      focusField(marketPriceRef);
+      submitLoading.value = false;
+      return;
+    }
+    if (!(member >= minSell)) {
+      ElMessage.warning('官网价必须大于等于最低售价');
+      focusField(minSellingPriceRef);
+      submitLoading.value = false;
+      return;
+    }
+    if (!(minSell >= maxPurch)) {
+      ElMessage.warning('最低售价必须大于等于最高采购价');
+      focusField(maxPurchasePriceRef);
+      submitLoading.value = false;
+      return;
+    }
+    if (!(maxPurch >= purch)) {
+      ElMessage.warning('最高采购价必须大于等于采购价');
+      focusField(purchasingPriceRef);
+      submitLoading.value = false;
+      return;
+    }
+    // 采购类价格需低于官网价
+    if (!(maxPurch < member)) {
+      ElMessage.warning('最高采购价需低于官网价');
+      focusField(maxPurchasePriceRef);
+      submitLoading.value = false;
+      return;
+    }
+    if (!(purch < member)) {
+      ElMessage.warning('采购价需低于官网价');
+      focusField(purchasingPriceRef);
+      submitLoading.value = false;
+      return;
     }
 
     // 准备提交数据,包含定制信息(A10产品名称由前端自动拼接,不传后端)
@@ -1336,8 +1518,8 @@ const handleSubmit = async () => {
       ...productForm,
       // 将服务保障ID数组转换为逗号分隔字符串
       serviceGuarantee: serviceGuarantees.value.map((id) => String(id)).join(','),
-      // 轮播图URL逗号分隔
-      imageUrl: carouselImages.value.join(','),
+      // 轮播图(ImageUpload 已是逗号分隔字符串)
+      imageUrl: carouselImages.value,
       // 将商品属性值转换为JSON字符串
       attributesList: JSON.stringify(productAttributesValues.value),
       isCustomize: customForm.isCustomize ? 1 : 0,
@@ -1389,6 +1571,41 @@ const handleUpcInput = () => {
   }
 };
 
+// 商品重量只允许数字和小数点(过滤中文及其他非法字符)
+const handleWeightInput = (val: string) => {
+  if (val !== undefined && val !== null) {
+    let v = String(val).replace(/[^\d.]/g, '');
+    // 只保留第一个小数点
+    const firstDot = v.indexOf('.');
+    if (firstDot !== -1) {
+      v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, '');
+    }
+    productForm.productWeight = v;
+  }
+};
+
+// 商品体积只允许数字和小数点
+const handleVolumeInput = (val: string) => {
+  if (val !== undefined && val !== null) {
+    let v = String(val).replace(/[^\d.]/g, '');
+    const firstDot = v.indexOf('.');
+    if (firstDot !== -1) {
+      v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, '');
+    }
+    productForm.productVolume = v;
+  }
+};
+
+// 参考链接过滤中文空格(全角空格 U+3000)
+const handleReferenceLinkInput = (val: string) => {
+  if (val !== undefined && val !== null) {
+    const filtered = String(val).replace(/\u3000/g, '');
+    if (filtered !== val) {
+      productForm.referenceLink = filtered;
+    }
+  }
+};
+
 // 销量人气只允许输入整数
 const handleSalesVolumeInput = (val: string) => {
   if (val !== undefined && val !== null && val !== '') {
@@ -1558,16 +1775,6 @@ const getStaffOptions = async () => {
   }
 };
 
-// 获取税率列表
-const getTaxRateOptions = async () => {
-  try {
-    const res = await getTaxRateList();
-    taxRateOptions.value = res.rows || [];
-  } catch (error) {
-    console.error('获取税率列表失败:', error);
-  }
-};
-
 // 加载分类属性列表
 const loadCategoryAttributes = async (categoryId: string | number) => {
   try {
@@ -1654,16 +1861,9 @@ const loadProductDetail = async () => {
         }
       }
 
-      // 回显税率 - 在税率选项中查找匹配的值(处理浮点数精度问题
+      // 回显税率(直接使用接口返回值,由税率编码带出,无需下拉匹配
       if (res.data.taxRate !== undefined && res.data.taxRate !== null) {
-        const apiTaxRate = Number(res.data.taxRate);
-        // 使用精度容差比较浮点数
-        const matchedOption = taxRateOptions.value.find((opt) => Math.abs(Number(opt.taxrate) - apiTaxRate) < 0.0001);
-        if (matchedOption) {
-          productForm.taxRate = matchedOption.taxrate;
-        } else {
-          productForm.taxRate = apiTaxRate;
-        }
+        productForm.taxRate = Number(res.data.taxRate);
       }
 
       // 回显单位 - 确保类型与下拉选项的id一致(数字类型)
@@ -1694,11 +1894,7 @@ const loadProductDetail = async () => {
       }
 
       // 回显轮播图
-      if (res.data.imageUrl) {
-        carouselImages.value = res.data.imageUrl.split(',').filter((url: string) => url.trim());
-      } else {
-        carouselImages.value = [];
-      }
+      carouselImages.value = res.data.imageUrl || '';
 
       // 回显分类选择
       categoryForm.topCategoryId = res.data.topCategoryId;
@@ -1843,7 +2039,6 @@ onMounted(async () => {
   await getUnitOptions();
   await getAfterSalesOptions();
   await getServiceGuaranteeOptions();
-  await getTaxRateOptions();
   // 先加载商品详情(如果是编辑模式)
   await loadProductDetail();
   // 再加载下拉选项,这样如果详情中没有值,会自动设置第一个

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

@@ -198,7 +198,7 @@
                 <span>{{ scope.row.totalInventory ?? '-' }}</span>
               </div>
               <div>
-                <span class="text-gray-500">可用库存:</span>
+                <span class="text-gray-500">现有库存:</span>
                 <span>{{ scope.row.nowInventory ?? '-' }}</span>
               </div>
               <div>
@@ -667,7 +667,7 @@ const inventoryForm = reactive<PriceInventoryForm>({
 
 const inventoryRules = {
   totalInventory: [{ required: true, message: '总库存不能为空', trigger: 'blur' }],
-  nowInventory: [{ required: true, message: '当前可用库存不能为空', trigger: 'blur' }],
+  nowInventory: [{ required: true, message: '当前现有库存不能为空', trigger: 'blur' }],
   virtualInventory: [{ required: true, message: '虚拟库存不能为空', trigger: 'blur' }]
 };
 

+ 2 - 2
src/views/product/base/selected.vue

@@ -180,7 +180,7 @@
                 <span>{{ scope.row.totalInventory ?? '-' }}</span>
               </div>
               <div>
-                <span class="text-gray-500">可用库存:</span>
+                <span class="text-gray-500">现有库存:</span>
                 <span>{{ scope.row.nowInventory ?? '-' }}</span>
               </div>
               <div>
@@ -663,7 +663,7 @@ const inventoryForm = reactive<PriceInventoryForm>({
 
 const inventoryRules = {
   totalInventory: [{ required: true, message: '总库存不能为空', trigger: 'blur' }],
-  nowInventory: [{ required: true, message: '当前可用库存不能为空', trigger: 'blur' }],
+  nowInventory: [{ required: true, message: '当前现有库存不能为空', trigger: 'blur' }],
   virtualInventory: [{ required: true, message: '虚拟库存不能为空', trigger: 'blur' }]
 };
 

+ 2 - 2
src/views/product/base/self.vue

@@ -180,7 +180,7 @@
                 <span>{{ scope.row.totalInventory ?? '-' }}</span>
               </div>
               <div>
-                <span class="text-gray-500">可用库存:</span>
+                <span class="text-gray-500">现有库存:</span>
                 <span>{{ scope.row.nowInventory ?? '-' }}</span>
               </div>
               <div>
@@ -662,7 +662,7 @@ const inventoryForm = reactive<PriceInventoryForm>({
 
 const inventoryRules = {
   totalInventory: [{ required: true, message: '总库存不能为空', trigger: 'blur' }],
-  nowInventory: [{ required: true, message: '当前可用库存不能为空', trigger: 'blur' }],
+  nowInventory: [{ required: true, message: '当前现有库存不能为空', trigger: 'blur' }],
   virtualInventory: [{ required: true, message: '虚拟库存不能为空', trigger: 'blur' }]
 };
 

+ 196 - 38
src/views/product/baseAudit/add.vue

@@ -188,6 +188,18 @@
               <div class="form-item-tip">A10产品名称由系统自动拼接:品牌名 + 规格型号 + 产品分类(三级分类)+ 发票规格,无需手动填写</div>
             </el-form-item>
 
+             <!-- 商品描述 -->
+            <el-form-item label="商品描述:">
+              <el-input
+                v-model="productForm.productDescription"
+                type="textarea"
+                :rows="3"
+                placeholder="请输入商品描述"
+                maxlength="500"
+                show-word-limit
+              />
+            </el-form-item>
+
             <!-- 规格型号 和 UPC(69)条码 -->
             <el-row :gutter="20">
               <el-col :span="12">
@@ -269,9 +281,13 @@
               </el-col>
               <el-col :span="12">
                 <el-form-item label="税率:" required>
-                  <el-select v-model="productForm.taxRate" placeholder="请选择税率" clearable class="w-full">
-                    <el-option v-for="option in taxRateOptions" :key="option.id" :label="`${option.taxrateNo},${option.taxrateName}`" :value="option.taxrate" />
-                  </el-select>
+                  <el-input
+                    :model-value="formatTaxRateDisplay(productForm.taxRate)"
+                    placeholder="由税率编码自动带出"
+                    readonly
+                    disabled
+                    class="w-full"
+                  />
                 </el-form-item>
               </el-col>
             </el-row>
@@ -311,9 +327,16 @@
               <el-input v-model="productForm.promotionTitle" type="textarea" :rows="3" placeholder="请输入促销标题" maxlength="300" show-word-limit />
             </el-form-item>
 
-            <!-- 商品简介 -->
-            <el-form-item label="商品简介:">
-              <el-input v-model="productForm.productDescription" type="textarea" :rows="3" placeholder="请输入商品简介" maxlength="500" show-word-limit />
+                        <!-- 商品说明 -->
+            <el-form-item label="商品说明:">
+              <el-input
+                v-model="productForm.productExplain"
+                type="textarea"
+                :rows="3"
+                placeholder="请输入商品说明"
+                maxlength="500"
+                show-word-limit
+              />
             </el-form-item>
 
 
@@ -321,7 +344,13 @@
             <el-row :gutter="20">
               <el-col :span="12">
                 <el-form-item label="商品重量:">
-                  <el-input v-model="productForm.productWeight" placeholder="0" maxlength="10" show-word-limit>
+                  <el-input
+                    v-model="productForm.productWeight"
+                    placeholder="0"
+                    maxlength="10"
+                    show-word-limit
+                    @input="handleNumericInput('productWeight')"
+                  >
                     <template #append>
                       <el-select v-model="productForm.weightUnit" placeholder="请选择" style="width: 100px">
                         <el-option label="kg" value="kg" />
@@ -334,7 +363,13 @@
               </el-col>
               <el-col :span="12">
                 <el-form-item label="商品体积:">
-                  <el-input v-model="productForm.productVolume" placeholder="0" maxlength="10" show-word-limit>
+                  <el-input
+                    v-model="productForm.productVolume"
+                    placeholder="0"
+                    maxlength="10"
+                    show-word-limit
+                    @input="handleNumericInput('productVolume')"
+                  >
                     <template #append>
                       <el-select v-model="productForm.volumeUnit" placeholder="请选择" style="width: 80px">
                         <el-option label="m³" value="m3" />
@@ -348,8 +383,15 @@
             </el-row>
 
             <!-- 参考链接 -->
-            <el-form-item label="参考链接">
-              <el-input v-model="productForm.referenceLink" type="textarea" :rows="3" placeholder="请输入参考链接" />
+            <el-form-item label="参考链接" prop="referenceLink" required>
+              <el-input
+                ref="referenceLinkRef"
+                v-model="productForm.referenceLink"
+                type="textarea"
+                :rows="3"
+                placeholder="请输入参考链接(不能包含中文空格)"
+                @input="handleReferenceLinkInput"
+              />
             </el-form-item>
 
 
@@ -399,6 +441,7 @@
               <el-col :span="8">
                 <el-form-item label="市场价:" prop="marketPrice" required>
                   <el-input
+                    ref="marketPriceRef"
                     v-model="productForm.marketPrice"
                     type="number"
                     placeholder="请输入市场价"
@@ -410,6 +453,7 @@
               <el-col :span="8">
                 <el-form-item label="官网价:" prop="memberPrice" required>
                   <el-input
+                    ref="memberPriceRef"
                     v-model="productForm.memberPrice"
                     type="number"
                     placeholder="请输入平台售价"
@@ -421,6 +465,7 @@
               <el-col :span="8">
                 <el-form-item label="最低售价:" prop="minSellingPrice" required>
                   <el-input
+                    ref="minSellingPriceRef"
                     v-model="productForm.minSellingPrice"
                     type="number"
                     placeholder="请输入最低售价"
@@ -438,7 +483,7 @@
               </el-col>
               <el-col :span="8">
                 <el-form-item label="备注:">
-                  <span class="currency-text">市场价>官网价>最低售价</span>
+                  <span class="currency-text">市场价 &gt; 官网价 ≥ 最低售价 ≥ 最高采购价 ≥ 采购价</span>
                 </el-form-item>
               </el-col>
             </el-row>
@@ -456,6 +501,7 @@
               <el-col :span="12">
                 <el-form-item label="采购价:" prop="purchasingPrice" required>
                   <el-input
+                    ref="purchasingPriceRef"
                     v-model="productForm.purchasingPrice"
                     type="number"
                     placeholder="请输入采购价"
@@ -465,8 +511,9 @@
                 </el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item label="最高采购价:">
+                <el-form-item label="最高采购价:" prop="maxPurchasePrice" required>
                   <el-input
+                    ref="maxPurchasePriceRef"
                     v-model="productForm.maxPurchasePrice"
                     type="number"
                     placeholder="请输入最高采购价"
@@ -628,9 +675,6 @@
                 <el-tab-pane label="电脑端详情" name="pc">
                   <Editor v-model="productForm.pcDetail" :height="400" />
                 </el-tab-pane>
-                <el-tab-pane label="移动端详情" name="mobile">
-                  <Editor v-model="productForm.mobileDetail" :height="400" />
-                </el-tab-pane>
               </el-tabs>
             </el-form-item>
           </el-form>
@@ -783,8 +827,7 @@ import {
   categoryAttributeList,
   getAfterSaleList,
   getServiceList,
-  getUnitList,
-  getTaxRateList
+  getUnitList
 } from '@/api/product/base';
 import {
   addBaseAudit,
@@ -807,6 +850,14 @@ const loading = ref(false);
 const submitLoading = ref(false);
 const productFormRef = ref();
 
+// 价格与参考链接输入框引用,用于校验失败时聚焦
+const marketPriceRef = ref();
+const memberPriceRef = ref();
+const minSellingPriceRef = ref();
+const maxPurchasePriceRef = ref();
+const purchasingPriceRef = ref();
+const referenceLinkRef = ref();
+
 // 服务保障和安装服务的多选框
 const serviceGuarantees = ref<(string | number)[]>([]);
 const installationServices = ref<string[]>([]);
@@ -817,8 +868,11 @@ const activeDetailTab = ref('pc');
 // 轮播图URL数组(UI管理用)
 const carouselImages = ref<string[]>([]);
 
-// 税率选项
-const taxRateOptions = ref<any[]>([]);
+// 税率显示格式化
+const formatTaxRateDisplay = (val: any) => {
+  if (val === undefined || val === null || val === '') return '';
+  return `${(Number(val) * 100).toFixed(0)}%`;
+};
 
 // 税率编码选择组件
 const taxCodeSelectRef = ref();
@@ -832,12 +886,16 @@ const handleTaxCodeSelect = async (row: any) => {
     const taxRes = await getTaxCode(row.id);
     if (taxRes.data) {
       taxCodeNo.value = `${taxRes.data.taxationNo},${taxRes.data.name}`;
+      // 自动带出税率(只读,不可手动修改)
+      productForm.taxRate = taxRes.data.taxrate;
     } else {
       taxCodeNo.value = row.taxationNo ? `${row.taxationNo},${row.name}` : (row.name || '');
+      productForm.taxRate = row.taxrate;
     }
   } catch (e) {
     console.error('获取税率编码详情失败:', e);
     taxCodeNo.value = row.taxationNo ? `${row.taxationNo},${row.name}` : (row.name || '');
+    productForm.taxRate = row.taxrate;
   }
   // 同时将显示值存入 form,方便编辑回显时直接读取
   (productForm as any).taxationNo = taxCodeNo.value;
@@ -1034,10 +1092,24 @@ const productRules = {
   itemName: [{ required: true, message: '商品名称不能为空', trigger: 'blur' }],
   brandId: [{ required: true, message: '商品品牌不能为空', trigger: 'change' }],
   // mainLibraryIntro: [{ required: true, message: '主供应商不能为空', trigger: 'change' }],
+  referenceLink: [
+    { required: true, message: '参考链接不能为空', trigger: 'blur' },
+    {
+      validator: (_rule: any, value: string, callback: any) => {
+        if (value && /[\u3000\u4e00-\u9fa5]/.test(value)) {
+          callback(new Error('参考链接不能包含中文或中文空格'));
+        } else {
+          callback();
+        }
+      },
+      trigger: 'blur'
+    }
+  ],
   marketPrice: [{ required: true, message: '市场价不能为空', trigger: 'blur' }],
   memberPrice: [{ required: true, message: '平台售价不能为空', trigger: 'blur' }],
   minSellingPrice: [{ required: true, message: '最低售价不能为空', trigger: 'blur' }],
   purchasingPrice: [{ required: true, message: '采购价不能为空', trigger: 'blur' }],
+  maxPurchasePrice: [{ required: true, message: '最高采购价不能为空', trigger: 'blur' }],
   productNature: [{ required: true, message: '产品经理不能为空', trigger: 'change' }],
   purchasingPersonnel: [{ required: true, message: '采购人员不能为空', trigger: 'change' }],
   taxRate: [{ required: true, message: '税率不能为空', trigger: 'change' }],
@@ -1319,22 +1391,95 @@ const handleSubmit = async () => {
       return;
     }
 
-    // 校验价格关系:市场价 > 官网价 > 最低售价
-    const midRange = parseFloat(String(productForm.marketPrice));
-    const standard = parseFloat(String(productForm.memberPrice));
-    const certificate = parseFloat(String(productForm.minSellingPrice));
-    if (!isNaN(midRange) && !isNaN(standard) && !isNaN(certificate)) {
-      if (!(midRange > standard)) {
-        ElMessage.warning('市场价必须大于官网价');
+    // 校验价格链:市场价 > 官网价 ≥ 最低售价 ≥ 最高采购价 ≥ 采购价,且均不为 0
+    const focusInput = async (refObj: any) => {
+      await nextTick();
+      try {
+        refObj?.value?.focus?.();
+      } catch (e) {
+        // 忽略聚焦异常
+      }
+    };
+    const priceFields: Array<{ key: string; label: string; ref: any }> = [
+      { key: 'marketPrice', label: '市场价', ref: marketPriceRef },
+      { key: 'memberPrice', label: '官网价', ref: memberPriceRef },
+      { key: 'minSellingPrice', label: '最低售价', ref: minSellingPriceRef },
+      { key: 'maxPurchasePrice', label: '最高采购价', ref: maxPurchasePriceRef },
+      { key: 'purchasingPrice', label: '采购价', ref: purchasingPriceRef }
+    ];
+    // 非空与非零校验
+    for (const f of priceFields) {
+      const raw = (productForm as any)[f.key];
+      if (raw === undefined || raw === null || raw === '') {
+        ElMessage.warning(`${f.label}不能为空`);
+        await focusInput(f.ref);
         submitLoading.value = false;
         return;
       }
-      if (!(standard > certificate)) {
-        ElMessage.warning('官网价必须大于最低售价');
+      const num = parseFloat(String(raw));
+      if (isNaN(num) || num <= 0) {
+        ElMessage.warning(`${f.label}不能为 0`);
+        await focusInput(f.ref);
         submitLoading.value = false;
         return;
       }
     }
+    const marketP = parseFloat(String(productForm.marketPrice));
+    const memberP = parseFloat(String(productForm.memberPrice));
+    const minSellP = parseFloat(String(productForm.minSellingPrice));
+    const maxPurP = parseFloat(String(productForm.maxPurchasePrice));
+    const purP = parseFloat(String(productForm.purchasingPrice));
+    if (!(marketP > memberP)) {
+      ElMessage.warning('市场价必须大于官网价');
+      await focusInput(marketPriceRef);
+      submitLoading.value = false;
+      return;
+    }
+    if (!(memberP >= minSellP)) {
+      ElMessage.warning('官网价必须大于或等于最低售价');
+      await focusInput(memberPriceRef);
+      submitLoading.value = false;
+      return;
+    }
+    if (!(minSellP >= maxPurP)) {
+      ElMessage.warning('最低售价必须大于或等于最高采购价');
+      await focusInput(minSellingPriceRef);
+      submitLoading.value = false;
+      return;
+    }
+    if (!(maxPurP >= purP)) {
+      ElMessage.warning('最高采购价必须大于或等于采购价');
+      await focusInput(maxPurchasePriceRef);
+      submitLoading.value = false;
+      return;
+    }
+    // 采购价与最高采购价均须低于官网价
+    if (!(maxPurP < memberP)) {
+      ElMessage.warning('最高采购价必须低于官网价');
+      await focusInput(maxPurchasePriceRef);
+      submitLoading.value = false;
+      return;
+    }
+    if (!(purP < memberP)) {
+      ElMessage.warning('采购价必须低于官网价');
+      await focusInput(purchasingPriceRef);
+      submitLoading.value = false;
+      return;
+    }
+
+    // 校验参考链接:必填且不能含中文或中文空格
+    if (!productForm.referenceLink || !String(productForm.referenceLink).trim()) {
+      ElMessage.warning('参考链接不能为空');
+      await focusInput(referenceLinkRef);
+      submitLoading.value = false;
+      return;
+    }
+    if (/[\u3000\u4e00-\u9fa5]/.test(String(productForm.referenceLink))) {
+      ElMessage.warning('参考链接不能包含中文或中文空格');
+      await focusInput(referenceLinkRef);
+      submitLoading.value = false;
+      return;
+    }
 
     // 准备提交数据,包含定制信息(A10产品名称由前端自动拼接,不传后端)
     const submitProductData: any = {
@@ -1395,6 +1540,27 @@ const handleUpcInput = () => {
   }
 };
 
+// 参考链接输入过滤:移除中文字符与中文全角空格(U+3000)
+const handleReferenceLinkInput = () => {
+  if (productForm.referenceLink) {
+    productForm.referenceLink = productForm.referenceLink.replace(/[\u3000\u4e00-\u9fa5]/g, '');
+  }
+};
+
+// 重量/体积输入过滤:仅保留数字与小数点
+const handleNumericInput = (field: 'productWeight' | 'productVolume') => {
+  const val = productForm[field];
+  if (val !== undefined && val !== null && val !== '') {
+    let str = String(val).replace(/[^\d.]/g, '');
+    // 仅保留首个小数点
+    const firstDot = str.indexOf('.');
+    if (firstDot !== -1) {
+      str = str.slice(0, firstDot + 1) + str.slice(firstDot + 1).replace(/\./g, '');
+    }
+    productForm[field] = str;
+  }
+};
+
 // 销量人气只允许输入整数
 const handleSalesVolumeInput = (val: string) => {
   if (val !== undefined && val !== null && val !== '') {
@@ -1568,15 +1734,7 @@ const getStaffOptions = async () => {
   }
 };
 
-// 获取税率列表
-const getTaxRateOptions = async () => {
-  try {
-    const res = await getTaxRateList();
-    taxRateOptions.value = res.rows || [];
-  } catch (error) {
-    console.error('获取税率列表失败:', error);
-  }
-};
+
 
 // 加载分类属性列表
 const loadCategoryAttributes = async (categoryId: string | number) => {
@@ -1853,7 +2011,7 @@ onMounted(async () => {
   await getUnitOptions();
   await getAfterSalesOptions();
   await getServiceGuaranteeOptions();
-  await getTaxRateOptions();
+
   // 先加载商品详情(如果是编辑模式)
   await loadProductDetail();
   // 再加载下拉选项,这样如果详情中没有值,会自动设置第一个

+ 24 - 71
src/views/product/baseAudit/index.vue

@@ -57,6 +57,14 @@
                   </el-select>
                 </el-form-item>
               </el-col>
+              <el-col v-if="queryParams.auditStatuss === '2,3'" :span="6">
+                <el-form-item label="审核状态" prop="auditStatus">
+                  <el-select v-model="queryParams.auditStatus" placeholder="请选择审核状态" clearable>
+                    <el-option label="审核通过" :value="2" />
+                    <el-option label="审核驳回" :value="3" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
               <el-col :span="6">
                 <el-form-item label="商品类型" prop="productStatus">
                   <el-select v-model="queryParams.productStatus" placeholder="请选择" clearable>
@@ -185,6 +193,13 @@
 <!--            <span>{{ scope.row.productBaseVo.dataSource || '-' }}</span>-->
 <!--          </template>-->
 <!--        </el-table-column>-->
+        <el-table-column v-if="queryParams.auditStatuss === '2,3'" label="审核状态" align="center" width="100" fixed="right">
+          <template #default="scope">
+            <el-tag v-if="scope.row.auditStatus === 2" type="success">审核通过</el-tag>
+            <el-tag v-else-if="scope.row.auditStatus === 3" type="danger">审核驳回</el-tag>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
         <el-table-column label="审核原因" align="center" prop="auditReason" width="150" fixed="right">
           <template #default="scope">
             <span>{{ scope.row.auditReason || '-' }}</span>
@@ -217,14 +232,11 @@
         </el-table-column>
       </el-table>
 
-      <!-- 游标分页控制 -->
       <pagination
-        v-show="baseList.length > 0"
+        v-show="total > 0"
         v-model:page="queryParams.pageNum"
         v-model:limit="queryParams.pageSize"
-        v-model:way="queryParams.way"
-        :cursor-mode="true"
-        :has-more="hasMore"
+        :total="total"
         @pagination="getList"
       />
     </el-card>
@@ -316,9 +328,7 @@ const total = ref(0);
 const brandOptions = ref<BrandVO[]>([]);
 const brandLoading = ref(false);
 const brandSearchTimer = ref<ReturnType<typeof setTimeout> | null>(null);
-const hasMore = ref(true); // 是否还有更多数据
-// 页面历史记录,存储每页的第一个 id 和最后一个 id,用于支持双向翻页
-const pageHistory = ref([]);
+
 
 // 三级分类选择组件引用
 const categoryCascadeRef = ref();
@@ -381,10 +391,9 @@ const data = reactive<PageData<BaseForm, BaseQuery>>({
     isSelf: undefined,
     productReviewStatus: undefined,
     productStatus: undefined,
-    lastSeenId: undefined, // 游标分页的lastSeenId
-    way: undefined,
     type: undefined,
     auditStatus: undefined,
+    auditStatuss: undefined,
     params: {}
   },
   rules: {
@@ -413,52 +422,8 @@ const getList = async () => {
   loading.value = true;
   try {
     initRouteParams();
-    const params = { ...queryParams.value };
-    const currentPageNum = queryParams.value.pageNum;
-
-    // 第一页不需要游标参数
-    if (currentPageNum === 1) {
-      delete params.lastSeenId;
-      delete params.way;
-    } else {
-      // way参数:0=上一页,1=下一页
-      if (queryParams.value.way === 0) {
-        // 上一页:使用目标页(即当前显示页)的firstId
-        const nextPageHistory = pageHistory.value[currentPageNum];
-        if (nextPageHistory) {
-          params.firstSeenId = nextPageHistory.firstId;
-          params.way = 0;
-        }
-      } else {
-        // 下一页:使用前一页的lastId作为lastSeenId
-        const prevPageHistory = pageHistory.value[currentPageNum - 1];
-        if (prevPageHistory) {
-          params.lastSeenId = prevPageHistory.lastId;
-          params.way = 1;
-        }
-      }
-    }
-
-    const res = await listBaseAudit(params);
+    const res = await listBaseAudit(queryParams.value);
     baseList.value = res.rows || [];
-
-    // 判断是否还有更多数据
-    hasMore.value = baseList.value.length === queryParams.value.pageSize;
-
-    // 记录当前页的第一个id和最后一个id
-    if (baseList.value.length > 0) {
-      const firstItem = baseList.value[0];
-      const lastItem = baseList.value[baseList.value.length - 1];
-      //如果长度小于currentPageNum则创建
-
-      if (pageHistory.value.length <= currentPageNum) {
-        pageHistory.value[currentPageNum] = {
-          firstId: firstItem.id,
-          lastId: lastItem.id
-        };
-      }
-    }
-
     total.value = res.total || 0;
   } catch (error) {
     console.error('获取列表失败:', error);
@@ -497,6 +462,9 @@ const initRouteParams = () => {
   if (route.query.auditStatus !== undefined && route.query.auditStatus !== '') {
     queryParams.value.auditStatus = Number(route.query.auditStatus);
   }
+  if (route.query.auditStatuss) {
+    queryParams.value.auditStatuss = route.query.auditStatuss as string;
+  }
 };
 
 /** 表单重置 */
@@ -507,20 +475,7 @@ const reset = () => {
 
 /** 搜索按钮操作 */
 const handleQuery = () => {
-  // 同步查询参数到游标分页参数
-  queryParams.value = {
-    ...queryParams.value,
-    pageNum: 1,
-    productNo: queryParams.value.productNo,
-    itemName: queryParams.value.itemName,
-    brandName: queryParams.value.brandName,
-    bottomCategoryId: queryParams.value.bottomCategoryId,
-    isSelf: queryParams.value.isSelf,
-    productReviewStatus: queryParams.value.productReviewStatus,
-    productStatus: queryParams.value.productStatus,
-    lastSeenId: undefined
-  };
-  pageHistory.value = []; // 重置页面历史
+  queryParams.value.pageNum = 1;
   getList();
 };
 
@@ -532,8 +487,6 @@ const resetQuery = () => {
   queryParams.value.mediumCategoryId = undefined;
   queryParams.value.bottomCategoryId = undefined;
   categoryCascadeRef.value?.reset();
-  queryParams.value.lastSeenId = undefined;
-  pageHistory.value = []; // 重置页面历史
   handleQuery();
 };
 

+ 3 - 11
src/views/product/poolAudit/index.vue

@@ -75,30 +75,22 @@
           </template>
         </el-table-column> -->
         <el-table-column label="申请时间" align="center" prop="createTime" width="180" />
+        <el-table-column label="审核时间" align="center" prop="auditTime" width="180" />
         <el-table-column label="操作" align="center" class-name="small-padding fixed-width" min-width="200">
           <template #default="scope">
             <!-- 审核通过或已驳回:只显示查看入池单 -->
-            <template v-if="scope.row.productReviewStatus == '2' || scope.row.productReviewStatus == '3'">
-              <el-button link type="primary" @click="handleAudit(scope.row)">查看入池单</el-button>
-            </template>
             <!-- 其他状态:显示原有操作 -->
-            <template v-else>
+              <el-button link type="primary" @click="handleAudit(scope.row)">查看入池单</el-button>
               <el-button
                 v-if="scope.row.productReviewStatus == '0' || scope.row.productReviewStatus === 0"
                 link type="primary"
                 @click="handleApply(scope.row)"
               >申请入池单</el-button>
-              <el-button
-                v-if="scope.row.productReviewStatus == '1' || scope.row.productReviewStatus === 1"
-                link type="warning"
-                @click="handleAudit(scope.row)"
-              >去审核</el-button>
               <el-button
                 v-if="scope.row.productReviewStatus == '0' || scope.row.productReviewStatus === 0"
                 link type="primary"
                 @click="handleUpdate(scope.row)"
               >修改入池单</el-button>
-            </template>
           </template>
         </el-table-column>
       </el-table>
@@ -394,7 +386,7 @@ const handleAudit = (row: PoolAuditVO) => {
 
 /** 申请入池单(待提交 -> 待审核) */
 const handleApply = async (row: PoolAuditVO) => {
-  await proxy?.$modal.confirm(`是否确认申请“${row.name}”入池单?确定后状态将变为待审核。`);
+  await proxy?.$modal.confirm(`是否确认提交入池单?`);
   await applyPoolAudit(row.id);
   proxy?.$modal.msgSuccess("申请入池单成功");
   await getList();

+ 1 - 1
src/views/product/poolAudit/itemAudit.vue

@@ -750,7 +750,7 @@ const inventoryForm = reactive<PriceInventoryForm>({
 
 const inventoryRules = {
   totalInventory: [{ required: true, message: '总库存不能为空', trigger: 'blur' }],
-  nowInventory: [{ required: true, message: '当前可用库存不能为空', trigger: 'blur' }],
+  nowInventory: [{ required: true, message: '当前现有库存不能为空', trigger: 'blur' }],
   virtualInventory: [{ required: true, message: '虚拟库存不能为空', trigger: 'blur' }]
 };
 

+ 2 - 2
src/views/product/poolAudit/marketingAudit.vue

@@ -185,7 +185,7 @@
                 <span>{{ scope.row.totalInventory ?? '-' }}</span>
               </div>
               <div>
-                <span class="text-gray-500">可用库存:</span>
+                <span class="text-gray-500">现有库存:</span>
                 <span>{{ scope.row.nowInventory ?? '-' }}</span>
               </div>
               <div>
@@ -735,7 +735,7 @@ const inventoryForm = reactive<PriceInventoryForm>({
 
 const inventoryRules = {
   totalInventory: [{ required: true, message: '总库存不能为空', trigger: 'blur' }],
-  nowInventory: [{ required: true, message: '当前可用库存不能为空', trigger: 'blur' }],
+  nowInventory: [{ required: true, message: '当前现有库存不能为空', trigger: 'blur' }],
   virtualInventory: [{ required: true, message: '虚拟库存不能为空', trigger: 'blur' }]
 };
 

+ 2 - 2
src/views/product/poolAudit/protocolAudit.vue

@@ -199,7 +199,7 @@
                 <span>{{ scope.row.totalInventory ?? '-' }}</span>
               </div>
               <div>
-                <span class="text-gray-500">可用库存:</span>
+                <span class="text-gray-500">现有库存:</span>
                 <span>{{ scope.row.nowInventory ?? '-' }}</span>
               </div>
               <div>
@@ -749,7 +749,7 @@ const inventoryForm = reactive<PriceInventoryForm>({
 
 const inventoryRules = {
   totalInventory: [{ required: true, message: '总库存不能为空', trigger: 'blur' }],
-  nowInventory: [{ required: true, message: '当前可用库存不能为空', trigger: 'blur' }],
+  nowInventory: [{ required: true, message: '当前现有库存不能为空', trigger: 'blur' }],
   virtualInventory: [{ required: true, message: '虚拟库存不能为空', trigger: 'blur' }]
 };
 

+ 2 - 2
src/views/product/poolAudit/selectedAudit.vue

@@ -185,7 +185,7 @@
                 <span>{{ scope.row.totalInventory ?? '-' }}</span>
               </div>
               <div>
-                <span class="text-gray-500">可用库存:</span>
+                <span class="text-gray-500">现有库存:</span>
                 <span>{{ scope.row.nowInventory ?? '-' }}</span>
               </div>
               <div>
@@ -735,7 +735,7 @@ const inventoryForm = reactive<PriceInventoryForm>({
 
 const inventoryRules = {
   totalInventory: [{ required: true, message: '总库存不能为空', trigger: 'blur' }],
-  nowInventory: [{ required: true, message: '当前可用库存不能为空', trigger: 'blur' }],
+  nowInventory: [{ required: true, message: '当前现有库存不能为空', trigger: 'blur' }],
   virtualInventory: [{ required: true, message: '虚拟库存不能为空', trigger: 'blur' }]
 };
 

+ 2 - 2
src/views/product/poolAudit/selfAudit.vue

@@ -185,7 +185,7 @@
                 <span>{{ scope.row.totalInventory ?? '-' }}</span>
               </div>
               <div>
-                <span class="text-gray-500">可用库存:</span>
+                <span class="text-gray-500">现有库存:</span>
                 <span>{{ scope.row.nowInventory ?? '-' }}</span>
               </div>
               <div>
@@ -659,7 +659,7 @@ const inventoryForm = reactive<PriceInventoryForm>({
 
 const inventoryRules = {
   totalInventory: [{ required: true, message: '总库存不能为空', trigger: 'blur' }],
-  nowInventory: [{ required: true, message: '当前可用库存不能为空', trigger: 'blur' }],
+  nowInventory: [{ required: true, message: '当前现有库存不能为空', trigger: 'blur' }],
   virtualInventory: [{ required: true, message: '虚拟库存不能为空', trigger: 'blur' }]
 };
 

+ 6 - 17
src/views/product/poolAuditReview/index.vue

@@ -35,9 +35,6 @@
       <template #header>
         <span class="font-bold">入池清单</span>
       </template>
-      <div class="mb-4" v-if="!auditInfoLoading && !isReadOnly">
-        <el-button type="danger" icon="Remove" @click="handleRemoveFromPoolListBatch">移出入池清单</el-button>
-      </div>
       <el-tabs v-model="activeTab" @tab-change="handleTabChange" class="mb-2">
         <el-tab-pane label="全部" name="" />
         <el-tab-pane label="审核通过" name="2" />
@@ -86,7 +83,7 @@
             </div>
           </template>
         </el-table-column>
-        <el-table-column label="项目/平台价" align="center" width="100">
+        <el-table-column label="项目/协议价" align="center" width="100">
           <template #default="scope">
             <span class="text-red-500">¥{{ scope.row.agreementPrice ?? scope.row.minSellingPrice ?? '0.00' }}</span>
           </template>
@@ -104,10 +101,10 @@
         </el-table-column>
         <el-table-column label="审核状态" align="center" width="100">
           <template #default="scope">
-            <el-tag v-if="(scope.row as any).auditStatus === '0'" type="info">待提交</el-tag>
-            <el-tag v-else-if="(scope.row as any).auditStatus === '1'" type="warning">待审核</el-tag>
-            <el-tag v-else-if="(scope.row as any).auditStatus === '2'" type="success">审核通过</el-tag>
-            <el-tag v-else-if="(scope.row as any).auditStatus === '3'" type="danger">审核驳回</el-tag>
+            <el-tag v-if="(scope.row as any).auditStatus == '0'" type="info">待提交</el-tag>
+            <el-tag v-else-if="(scope.row as any).auditStatus == '1'" type="warning">待审核</el-tag>
+            <el-tag v-else-if="(scope.row as any).auditStatus == '2'" type="success">审核通过</el-tag>
+            <el-tag v-else-if="(scope.row as any).auditStatus == '3'" type="danger">审核驳回</el-tag>
             <span v-else>-</span>
           </template>
         </el-table-column>
@@ -116,11 +113,6 @@
             <span>{{ (scope.row as any).auditReason || '-' }}</span>
           </template>
         </el-table-column>
-        <el-table-column v-if="!auditInfoLoading && !isReadOnly" label="操作" align="center" width="80" fixed="right">
-          <template #default="scope">
-            <el-link type="danger" :underline="false" @click="handleRemoveFromPoolList(scope.row)">移除</el-link>
-          </template>
-        </el-table-column>
       </el-table>
       <pagination
         v-show="listTotal > 0"
@@ -132,10 +124,7 @@
     </el-card>
 
     <!-- 底部操作按钮 -->
-    <div v-if="!auditInfoLoading && !isReadOnly" class="fixed bottom-0 left-0 right-0 bg-white border-t p-4 flex justify-center gap-4 z-10">
-      <el-button type="danger" size="large" @click="handleReject">驳 回</el-button>
-      <el-button type="primary" size="large" @click="handlePass">通 过</el-button>
-    </div>
+
 
     <!-- 驳回原因对话框 -->
     <el-dialog title="驳回原因" v-model="rejectDialog.visible" width="500px" append-to-body>

+ 188 - 62
src/views/product/poolLinkAudit/index.vue

@@ -31,7 +31,7 @@
             <!-- type 2:协议产品池 -->
             <el-form-item v-else-if="auditForm.type === '2'" label="协议" prop="protocolId">
               <el-select v-model="auditForm.protocolId" placeholder="请选择协议" clearable filterable style="width: 100%">
-                <el-option v-for="item in protocolOptions" :key="item.id" :label="`${item.protocolNo} - ${item.customerName}`" :value="item.id" />
+                <el-option v-for="item in protocolOptions" :key="item.id" :label="`${item.customerName}`" :value="item.id" />
               </el-select>
             </el-form-item>
             <!-- type 3:项目产品池 -->
@@ -140,6 +140,7 @@
               size="small"
               style="width: 120px"
               placeholder="请输入协议价"
+              @blur="handleAgreementPriceBlur(scope.row)"
             />
           </template>
         </el-table-column>
@@ -349,7 +350,7 @@
                   <span>{{ scope.row.totalInventory ?? '-' }}</span>
                 </div>
                 <div>
-                  <span class="text-gray-500">可用库存:</span>
+                  <span class="text-gray-500">现有库存:</span>
                   <span>{{ scope.row.nowInventory ?? '-' }}</span>
                 </div>
                 <div>
@@ -381,11 +382,10 @@
               </div>
             </template>
           </el-table-column>
-          <el-table-column label="操作" align="center" width="100" fixed="right">
+          <el-table-column label="操作" align="center" width="120" fixed="right">
             <template #default="scope">
-              <el-link type="primary" :underline="false" @click="handleAddSingleProduct(scope.row)" :disabled="tempProductIds.includes(scope.row.id)"
-                >加入清单</el-link
-              >
+              <el-tag v-if="isProductSelected(scope.row.id)" type="success" size="small">已选({{ getPoolTypeLabel() }})</el-tag>
+              <el-link v-else type="primary" :underline="false" @click="handleAddSingleProduct(scope.row)">加入清单</el-link>
             </template>
           </el-table-column>
         </el-table>
@@ -426,6 +426,9 @@
         <el-form-item v-if="thirdPartyDialog.pricingRule === '1'" label="折扣率:">
           <span>{{ thirdPartyDialog.discountRate !== undefined ? thirdPartyDialog.discountRate : '-' }}</span>
         </el-form-item>
+        <el-form-item label="起订量:">
+          <el-input-number v-model="thirdPartyDialog.minOrderQuantity" :min="1" controls-position="right" style="width: 100%" />
+        </el-form-item>
         <el-form-item label="* 第三方平台售价:">
           <el-input-number
             v-model="thirdPartyDialog.negotiatedPrice"
@@ -434,6 +437,7 @@
             :min="0"
             controls-position="right"
             style="width: 100%"
+            @blur="handleThirdPartyPriceBlur"
           />
         </el-form-item>
         <el-form-item label="市场价:">
@@ -442,16 +446,14 @@
         <el-form-item label="官网价:">
           <span>{{ thirdPartyDialog.row?.memberPrice || 0 }}</span>
         </el-form-item>
-        <el-form-item label="起订量:">
-          <el-input-number v-model="thirdPartyDialog.minOrderQuantity" :min="1" controls-position="right" style="width: 100%" />
-        </el-form-item>
+
         <el-form-item label="最低售价:">
           <span class="text-red-500">¥{{ thirdPartyDialog.row?.minSellingPrice || 0 }}</span>
         </el-form-item>
       </el-form>
       <div class="mx-1 mb-3 px-3 py-2 rounded flex items-center gap-1 text-sm" style="background: #fff9f0; border: 1px solid #ffd591; color: #d48806">
         <el-icon><i-ep-warning /></el-icon>
-        <span>(当选价不能低于最低售价,不低于官网价)</span>
+        <span>(第三方平台售价不能低于最低售价,不高于官网价)</span>
       </div>
       <template #footer>
         <el-button @click="thirdPartyDialog.visible = false">返回</el-button>
@@ -464,6 +466,7 @@
 <script setup name="PoolLinkAudit" lang="ts">
 import { useRouter, useRoute } from 'vue-router';
 import { categoryTree, listBase } from '@/api/product/base';
+import { getPoolAuditProducts } from '@/api/product/baseAudit';
 import { BaseVO, BaseQuery } from '@/api/product/base/types';
 import { getPoolAudit, updatePoolAudit, addPoolAudit } from '@/api/product/poolAudit';
 import { listInfo } from '@/api/customer/supplierInfo';
@@ -726,19 +729,34 @@ const loadAuditInfo = async () => {
     auditForm.remark = data.remark;
     auditForm.attachment = data.attachment;
     auditForm.applyType = data.applyType;
-    isInitializing.value = false;
 
     // 加载对应下拉选项
     if (auditForm.type) {
       await loadSelectOptions(auditForm.type);
     }
 
+    // 修复类型不匹配导致 el-select 无法回显的问题(接口返回的 id 类型可能与选项列表不一致)
+    if (auditForm.type === '2' && auditForm.protocolId) {
+      const match = protocolOptions.value.find((item) => String(item.id) === String(auditForm.protocolId));
+      if (match) auditForm.protocolId = match.id;
+    } else if (auditForm.type === '3' && auditForm.itemId) {
+      const match = itemOptions.value.find((item) => String(item.id) === String(auditForm.itemId));
+      if (match) auditForm.itemId = match.id;
+    } else if (auditForm.type === '4' && auditForm.poolId) {
+      const match = poolOptions.value.find((item) => String(item.id) === String(auditForm.poolId));
+      if (match) auditForm.poolId = match.id;
+    }
+
+    // 所有回显赋值完成后再关闭标志位,避免 watch 异步回调清空字段
+    isInitializing.value = false;
+
     // 从 products 初始化商品 ID 列表和协议价
-    const products: Array<{ productId: string | number; agreementPrice?: number }> = (data as any).products || [];
+    const products: Array<{ productId: string | number; negotiatedPrice?: number; agreementPrice?: number }> = (data as any).products || [];
     tempProductIds.value = products.map((p: any) => p.productId).join(',');
     products.forEach((p: any) => {
-      if (p.agreementPrice !== undefined && p.agreementPrice !== null) {
-        tempAgreementPrices[p.productId] = p.agreementPrice;
+      const price = p.negotiatedPrice ?? p.agreementPrice;
+      if (price !== undefined && price !== null) {
+        tempAgreementPrices[p.productId] = price;
       }
     });
 
@@ -901,6 +919,27 @@ const handleSubmitAudit = async () => {
     return;
   }
 
+  // type=2 协议产品池:提交前校验所有商品的协议价
+  if (auditForm.type === '2') {
+    for (const product of productList.value) {
+      const price = tempAgreementPrices[product.id];
+      if (!price || price <= 0) {
+        proxy?.$modal.msgWarning(`商品「${product.itemName || product.productNo}」的协议价不能为0,请检查后重试`);
+        return;
+      }
+      const minPrice = Number(product.minSellingPrice) || 0;
+      const maxPrice = Number(product.memberPrice) || 0;
+      if (minPrice > 0 && price < minPrice) {
+        proxy?.$modal.msgWarning(`商品「${product.itemName || product.productNo}」的协议价不能低于最低售价(¥${minPrice})`);
+        return;
+      }
+      if (maxPrice > 0 && price > maxPrice) {
+        proxy?.$modal.msgWarning(`商品「${product.itemName || product.productNo}」的协议价不能高于官网价(¥${maxPrice})`);
+        return;
+      }
+    }
+  }
+
   await proxy?.$modal.confirm('确认要提交产品池审核单据吗?');
 
   try {
@@ -1113,59 +1152,86 @@ const handleAddProduct = () => {
 const getProductList = async () => {
   addProductDialog.loading = true;
   try {
-    const params = { ...addProductQuery.value };
-    // 只查询已上架的商品
-    params.productStatus = 1;
-    // 自营池(type=0)查询非自营商品;标准产品池(type=1)或协议产品池(type=2)查询自营商品
-    if (auditForm.type === '0') {
-      params.isSelf = 0;
-    } else if (auditForm.type === '1' || auditForm.type === '2') {
-      params.isSelf = 1;
-    }
-    const currentPageNum = addProductQuery.value.pageNum;
-
-    if (currentPageNum === 1) {
-      delete params.lastSeenId;
-      delete params.firstSeenId;
-      delete params.way;
+    // 出池模式:使用 getPoolAuditProducts 接口
+    if (auditForm.applyType === 1) {
+      const outParams: any = {
+        pageNum: addProductQuery.value.pageNum,
+        pageSize: addProductQuery.value.pageSize,
+        poolId: auditForm.poolId,
+        itemId: auditForm.itemId,
+        type: auditForm.type,
+        protocolId: auditForm.protocolId,
+        productNo: addProductQuery.value.productNo,
+        productName: addProductQuery.value.itemName
+      };
+      const res = await getPoolAuditProducts(outParams);
+      if (res.rows) {
+        addProductDialog.productList = res.rows;
+        addProductDialog.total = res.total || 0;
+      } else if (res.data) {
+        addProductDialog.productList = Array.isArray(res.data) ? res.data : [];
+        addProductDialog.total = addProductDialog.productList.length;
+      } else {
+        addProductDialog.productList = [];
+        addProductDialog.total = 0;
+      }
+      addProductHasMore.value = addProductDialog.productList.length === addProductQuery.value.pageSize;
     } else {
-      if (addProductQuery.value.way === 0) {
-        const nextPageHistory = addProductPageHistory.value[currentPageNum];
-        if (nextPageHistory) {
-          params.firstSeenId = nextPageHistory.firstId;
-          params.way = 0;
-        }
+      // 入池模式:使用 listBase 接口
+      const params = { ...addProductQuery.value };
+      // 只查询已上架的商品
+      params.productStatus = 1;
+      // 自营池(type=0)查询非自营商品;标准产品池(type=1)或协议产品池(type=2)查询自营商品
+      if (auditForm.type === '0') {
+        params.isSelf = 0;
+      } else if (auditForm.type === '1' || auditForm.type === '2') {
+        params.isSelf = 1;
+      }
+      const currentPageNum = addProductQuery.value.pageNum;
+
+      if (currentPageNum === 1) {
+        delete params.lastSeenId;
+        delete params.firstSeenId;
+        delete params.way;
       } else {
-        const prevPageHistory = addProductPageHistory.value[currentPageNum - 1];
-        if (prevPageHistory) {
-          params.lastSeenId = prevPageHistory.lastId;
-          params.way = 1;
+        if (addProductQuery.value.way === 0) {
+          const nextPageHistory = addProductPageHistory.value[currentPageNum];
+          if (nextPageHistory) {
+            params.firstSeenId = nextPageHistory.firstId;
+            params.way = 0;
+          }
+        } else {
+          const prevPageHistory = addProductPageHistory.value[currentPageNum - 1];
+          if (prevPageHistory) {
+            params.lastSeenId = prevPageHistory.lastId;
+            params.way = 1;
+          }
         }
       }
-    }
 
-    const res = await listBase(params);
-    if (res.rows) {
-      addProductDialog.productList = res.rows;
-      addProductDialog.total = res.total || 0;
-    } else if (res.data) {
-      addProductDialog.productList = Array.isArray(res.data) ? res.data : [];
-      addProductDialog.total = addProductDialog.productList.length;
-    } else {
-      addProductDialog.productList = [];
-      addProductDialog.total = 0;
-    }
+      const res = await listBase(params);
+      if (res.rows) {
+        addProductDialog.productList = res.rows;
+        addProductDialog.total = res.total || 0;
+      } else if (res.data) {
+        addProductDialog.productList = Array.isArray(res.data) ? res.data : [];
+        addProductDialog.total = addProductDialog.productList.length;
+      } else {
+        addProductDialog.productList = [];
+        addProductDialog.total = 0;
+      }
 
-    addProductHasMore.value = addProductDialog.productList.length === addProductQuery.value.pageSize;
+      addProductHasMore.value = addProductDialog.productList.length === addProductQuery.value.pageSize;
 
-    if (addProductDialog.productList.length > 0) {
-      const firstItem = addProductDialog.productList[0];
-      const lastItem = addProductDialog.productList[addProductDialog.productList.length - 1];
-      if (addProductPageHistory.value.length <= currentPageNum) {
-        addProductPageHistory.value[currentPageNum] = {
-          firstId: firstItem.id,
-          lastId: lastItem.id
-        };
+      if (addProductDialog.productList.length > 0) {
+        const firstItem = addProductDialog.productList[0];
+        const lastItem = addProductDialog.productList[addProductDialog.productList.length - 1];
+        if (addProductPageHistory.value.length <= currentPageNum) {
+          addProductPageHistory.value[currentPageNum] = {
+            firstId: firstItem.id,
+            lastId: lastItem.id
+          };
+        }
       }
     }
     // 初始化协议价(只对type=2有意义,默认用官网价)
@@ -1276,11 +1342,14 @@ const handleCategoryChange = (value: (string | number)[]) => {
   }
 };
 
-/** 切换价格模式时重新计算售价 */
+/** 切换价格模式时清空价格并重新计算 */
 watch(
   () => thirdPartyDialog.pricingRule,
   (mode) => {
-    if (mode === 'discount') {
+    // 切换模式时先清空价格
+    thirdPartyDialog.negotiatedPrice = 0;
+    // 折扣率模式:自动计算 第三方平台售价 = 官网价 × 折扣率
+    if (mode === '1') {
       const memberPrice = Number(thirdPartyDialog.row?.memberPrice) || 0;
       const rate = thirdPartyDialog.discountRate || 0;
       thirdPartyDialog.negotiatedPrice = parseFloat((memberPrice * rate).toFixed(2));
@@ -1288,6 +1357,24 @@ watch(
   }
 );
 
+/** 第三方售价失焦校验:不低于最低售价,不高于官网价 */
+const handleThirdPartyPriceBlur = () => {
+  const price = thirdPartyDialog.negotiatedPrice;
+  const row = thirdPartyDialog.row;
+  if (!row || !price || price <= 0) return;
+  const minPrice = Number(row.minSellingPrice) || 0;
+  const maxPrice = Number(row.memberPrice) || 0;
+  if (minPrice > 0 && price < minPrice) {
+    proxy?.$modal.msgWarning(`第三方售价不能低于最低售价(¥${minPrice})`);
+    thirdPartyDialog.negotiatedPrice = minPrice;
+    return;
+  }
+  if (maxPrice > 0 && price > maxPrice) {
+    proxy?.$modal.msgWarning(`第三方售价不能高于官网价(¥${maxPrice})`);
+    thirdPartyDialog.negotiatedPrice = maxPrice;
+  }
+};
+
 /** 确认选择第三方产品分类,加入入池清单 */
 const handleConfirmThirdParty = async () => {
   if (!thirdPartyDialog.categoryValue || thirdPartyDialog.categoryValue.length === 0) {
@@ -1334,6 +1421,45 @@ const getCategoryName = (row: BaseVO): string => {
   return '-';
 };
 
+/** 判断商品是否已在入池清单中 */
+const isProductSelected = (id: string | number): boolean => {
+  if (!tempProductIds.value) return false;
+  return tempProductIds.value.split(',').includes(String(id));
+};
+
+/** 获取当前产品池类型标签 */
+const getPoolTypeLabel = (): string => {
+  const typeMap: Record<string, string> = {
+    '0': '自营池',
+    '1': '标准池',
+    '2': '协议池',
+    '3': '项目池',
+    '4': '营销池'
+  };
+  return typeMap[auditForm.type || ''] || '';
+};
+
+/** 协议价失焦校验 */
+const handleAgreementPriceBlur = (row: BaseVO) => {
+  const price = tempAgreementPrices[row.id];
+  if (price === undefined || price === null || price <= 0) {
+    proxy?.$modal.msgWarning('协议价不能为0');
+    tempAgreementPrices[row.id] = undefined;
+    return;
+  }
+  const minPrice = Number(row.minSellingPrice) || 0;
+  const maxPrice = Number(row.memberPrice) || 0;
+  if (minPrice > 0 && price < minPrice) {
+    proxy?.$modal.msgWarning(`协议价不能低于最低售价(¥${minPrice})`);
+    tempAgreementPrices[row.id] = minPrice;
+    return;
+  }
+  if (maxPrice > 0 && price > maxPrice) {
+    proxy?.$modal.msgWarning(`协议价不能高于官网价(¥${maxPrice})`);
+    tempAgreementPrices[row.id] = maxPrice;
+  }
+};
+
 /** 移除商品(本地移除) */
 const handleRemoveProduct = async (row: BaseVO) => {
   await proxy?.$modal.confirm('确认要移除该商品吗?');

+ 15 - 10
src/views/product/pptScheme/index.vue

@@ -66,9 +66,10 @@
             {{ scope.row.products.length }}
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="380" fixed="right">
+        <el-table-column label="操作" width="440" fixed="right">
           <template #default="scope">
             <el-button link type="primary" @click="handleDownload(scope.row)">下载</el-button>
+            <el-button link type="primary" @click="handleExportProducts(scope.row)">导出商品</el-button>
             <el-button link type="primary" @click="handlePreview(scope.row)">预览</el-button>
             <el-button link type="success" @click="openShareDrawer(scope.row)">分享</el-button>
             <el-divider direction="vertical" />
@@ -305,6 +306,7 @@
                   @selection-change="handleSelectedRowsChange"
                 >
                   <el-table-column type="selection" width="55" />
+                  <el-table-column prop="productNo" label="产品编号" width="120" />
                   <el-table-column label="商品图" width="90">
                     <template #default="scope">
                       <el-image :src="scope.row.productImage" class="picker-prod-thumb" fit="cover" />
@@ -317,13 +319,13 @@
                       <el-tag size="small" type="info" plain>{{ scope.row.topCategoryName+'-'+scope.row.mediumCategoryName+'-'+ scope.row.bottomCategoryName}}</el-tag>
                     </template>
                   </el-table-column>
-                  <el-table-column prop="productNo" label="产品编号" width="120" />
+                  
                   <el-table-column prop="specificationsCode" label="型号/规格" width="120" show-overflow-tooltip />
                   <el-table-column label="价格对比" width="140">
                     <template #default="scope">
                       <div class="price-compare">
-                        <div class="m-price">市: ¥{{ scope.row.marketPrice }}</div>
-                        <div class="p-price">供: ¥{{ scope.row.marketPrice }}</div>
+                        <div class="m-price">市场价: ¥{{ scope.row.marketPrice }}</div>
+                        <div class="p-price">官网价: ¥{{ scope.row.memberPrice }}</div>
                       </div>
                     </template>
                   </el-table-column>
@@ -466,7 +468,7 @@
               <el-image :src="scope.row.productImage" fit="cover" class="picker-prod-thumb" />
             </template>
           </el-table-column>
-          <el-table-column prop="productNo" label="编号" width="100" />
+          <el-table-column prop="productNo" label="产品编号" width="120" />
           <el-table-column prop="itemName" label="商品名称" min-width="180" show-overflow-tooltip />
           <el-table-column prop="brandName" label="品牌" width="100" />
           <el-table-column label="所属分类" width="150" show-overflow-tooltip>
@@ -474,13 +476,12 @@
               <el-tag size="small" type="info" plain>{{ scope.row.topCategoryName+'-'+scope.row.mediumCategoryName+'-'+ scope.row.bottomCategoryName}}</el-tag>
             </template>
           </el-table-column>
-          <el-table-column prop="productNo" label="产品编号" width="120" />
           <el-table-column prop="specificationsCode" label="型号/规格" width="120" show-overflow-tooltip />
           <el-table-column label="价格对比" width="140">
             <template #default="scope">
               <div class="price-compare">
-                <div class="m-price">市: ¥{{ scope.row.marketPrice }}</div>
-                <div class="p-price">供: ¥{{ scope.row.marketPrice }}</div>
+                <div class="m-price">市场价: ¥{{ scope.row.marketPrice }}</div>
+                <div class="p-price">官网价: ¥{{ scope.row.memberPrice }}</div>
               </div>
             </template>
           </el-table-column>
@@ -731,7 +732,7 @@ import { listPptTemplate } from '@/api/product/pptTemplate';
 import { listBase } from '@/api/product/base';
 import { listUser } from '@/api/system/user';
 import { useUserStore } from '@/store/modules/user';
-import request, { globalHeaders } from '@/utils/request';
+import request, { globalHeaders, download } from '@/utils/request';
 
 // ==================== 数据转换工具函数 ====================
 
@@ -1094,7 +1095,7 @@ const loadAllData = async () => {
       query.createBy = userStore.userId;
     } else if (activeTab.value === 'sharedToMe') {
       // 分享给我的:ownerId = 当前用户 ID 且 isShared = '1'
-      query.ownerId = userStore.userId;
+      query.sharerId = userStore.userId;
       query.isShared = '1';
     }
     const schemeRes = await listPptScheme(query);
@@ -1172,6 +1173,10 @@ const handleBatchDownload = async () => {
   }
 };
 
+const handleExportProducts = (scheme) => {
+  download('/product/pptTemplate/exportSchemeProducts', { schemeId: scheme.id }, `${scheme.name || 'PPT方案'}_商品导出.xlsx`);
+};
+
 const handlePreview = (row) => {
   previewData.value = row;
   previewVisible.value = true;

+ 4 - 2
src/views/product/pptTemplate/index.vue

@@ -200,12 +200,14 @@
 
 <script setup lang="ts">
 import { ref, computed, onMounted } from 'vue';
+import { useRoute } from 'vue-router';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { Plus, Flag, Picture, Monitor, Operation } from '@element-plus/icons-vue';
 import { listPptTemplate, addPptTemplate, updatePptTemplate, delPptTemplate } from '@/api/product/pptTemplate/index';
-import type { PptTemplateVO, PptTemplateForm } from '@/api/product/pptTemplate/types';
+import type { PptTemplateVO, PptTemplateForm, PptTemplateQuery } from '@/api/product/pptTemplate/types';
 import request from '@/utils/request';
 
+const route = useRoute();
 const templateList = ref<PptTemplateVO[]>([]);
 const dialogVisible = ref(false);
 const isEdit = ref(false);
@@ -248,7 +250,7 @@ const rules = {
 const loadTemplates = async () => {
   loading.value = true;
   try {
-    const res = await listPptTemplate();
+    const res = await listPptTemplate({ myTemplate: route.query.myTemplate as string } as PptTemplateQuery);
     templateList.value = res.rows || [];
   } finally {
     loading.value = false;

+ 5 - 5
src/views/product/priceInventory/index.vue

@@ -22,8 +22,8 @@
             <el-form-item label="总库存量" prop="totalInventory">
               <el-input v-model="queryParams.totalInventory" placeholder="请输入总库存量" clearable @keyup.enter="handleQuery" />
             </el-form-item>
-            <el-form-item label="当前可用库存" prop="nowInventory">
-              <el-input v-model="queryParams.nowInventory" placeholder="请输入当前可用库存" clearable @keyup.enter="handleQuery" />
+            <el-form-item label="当前现有库存" prop="nowInventory">
+              <el-input v-model="queryParams.nowInventory" placeholder="请输入当前现有库存" clearable @keyup.enter="handleQuery" />
             </el-form-item>
             <el-form-item label="虚拟库存" prop="virtualInventory">
               <el-input v-model="queryParams.virtualInventory" placeholder="请输入虚拟库存" clearable @keyup.enter="handleQuery" />
@@ -77,7 +77,7 @@
         <el-table-column label="采购价格" align="center" prop="purchasingPrice" />
         <el-table-column label="最高采购价格" align="center" prop="maxPurchasePrice" />
         <el-table-column label="总库存量" align="center" prop="totalInventory" />
-        <el-table-column label="当前可用库存" align="center" prop="nowInventory" />
+        <el-table-column label="当前现有库存" align="center" prop="nowInventory" />
         <el-table-column label="虚拟库存" align="center" prop="virtualInventory" />
         <el-table-column label="最小起订数量" align="center" prop="minOrderQuantity" />
         <el-table-column label="税率" align="center" prop="taxRate" />
@@ -118,8 +118,8 @@
         <el-form-item label="总库存量" prop="totalInventory">
           <el-input v-model="form.totalInventory" placeholder="请输入总库存量" />
         </el-form-item>
-        <el-form-item label="当前可用库存" prop="nowInventory">
-          <el-input v-model="form.nowInventory" placeholder="请输入当前可用库存" />
+        <el-form-item label="当前现有库存" prop="nowInventory">
+          <el-input v-model="form.nowInventory" placeholder="请输入当前现有库存" />
         </el-form-item>
         <el-form-item label="虚拟库存" prop="virtualInventory">
           <el-input v-model="form.virtualInventory" placeholder="请输入虚拟库存" />

+ 33 - 3
src/views/product/protocolInfo/index.vue

@@ -84,7 +84,7 @@
           </el-select>
         </el-form-item>
         <el-form-item label="所属公司" prop="companyId">
-          <el-input v-model="form.customerName" disabled />
+          <el-input v-model="form.companyName" disabled />
         </el-form-item>
         <el-row :gutter="20">
           <el-col :span="12">
@@ -298,9 +298,9 @@ const handleCustomerChange = (customerId: string | number) => {
   if (customer) {
     form.value.customerNo = customer.customerNo;
     form.value.customerName = customer.customerName;
-    // 公司名称显示客户名称
+    // 公司名称
     form.value.companyId = customer.belongCompanyId;
-    form.value.companyName = customer.customerName;
+    form.value.companyName = customer.companyName;
 
     // 从客户信息中获取业务员和客服信息
     if (customer.customerSalesInfoVo) {
@@ -362,6 +362,21 @@ const handleUpdate = async (row?: InfoVO) => {
   const _id = row?.id || ids.value[0]
   const res = await getInfo(_id);
   Object.assign(form.value, res.data);
+  // 如果API未返回名称字段,从客户列表查找回填
+  if (form.value.customerId) {
+    const customer = customerList.value.find(item => item.id === form.value.customerId);
+    if (customer) {
+      if (!form.value.salesmanName && customer.customerSalesInfoVo) {
+        form.value.salesmanName = customer.customerSalesInfoVo.salesPerson || '';
+      }
+      if (!form.value.serviceName && customer.customerSalesInfoVo) {
+        form.value.serviceName = customer.customerSalesInfoVo.serviceStaff || '';
+      }
+      if (!form.value.companyName) {
+        form.value.companyName = customer.companyName || '';
+      }
+    }
+  }
   dialog.visible = true;
   dialog.title = "修改协议信息";
   dialog.isView = false;
@@ -416,6 +431,21 @@ const handleDetail = async (row: InfoVO) => {
   reset();
   const res = await getInfo(row.id);
   Object.assign(form.value, res.data);
+  // 如果API未返回名称字段,从客户列表查找回填
+  if (form.value.customerId) {
+    const customer = customerList.value.find(item => item.id === form.value.customerId);
+    if (customer) {
+      if (!form.value.salesmanName && customer.customerSalesInfoVo) {
+        form.value.salesmanName = customer.customerSalesInfoVo.salesPerson || '';
+      }
+      if (!form.value.serviceName && customer.customerSalesInfoVo) {
+        form.value.serviceName = customer.customerSalesInfoVo.serviceStaff || '';
+      }
+      if (!form.value.companyName) {
+        form.value.companyName = customer.companyName || '';
+      }
+    }
+  }
   dialog.visible = true;
   dialog.title = "查看基本信息";
   dialog.isView = true;