Prechádzať zdrojové kódy

feat(product): 优化商品管理功能

- 在商品品牌和单位下拉选项中同时显示编号和名称
- 将商品属性值输入方式改为标签式输入界面
- 优化税率选项显示格式,包含编号和名称
- 移除商品审核状态字段验证和相关表格列
- 添加商品主图、轮播图、详情页必填校验
- 修复商品详情页路由参数传递问题
- 优化商品属性输入组件,支持动态标签管理
- 更新税务代码选择组件,仅显示叶子节点
- 调整商品审核流程,修改库存操作逻辑
- 更新上传图片组件,增加编辑功能图标
- 重构商品属性表单验证规则
- 优化税务编码显示格式化处理
- 调整商品池审核页面布局和状态显示
- 添加附件文件列表展示功能
- 优化商品类别和审核状态相关字段配置
- 调整商品操作按钮权限控制逻辑
肖路 1 mesiac pred
rodič
commit
bb023292b2

+ 7 - 2
src/api/product/attributes/types.ts

@@ -67,6 +67,11 @@ export interface AttributesForm extends BaseEntity {
    */
   categoryId?: string | number;
 
+  /**
+   * 产品分类名称
+   */
+  categoryName?: string;
+
   /**
    * 属性编码(用于系统识别)
    */
@@ -83,7 +88,7 @@ export interface AttributesForm extends BaseEntity {
   isOptional?: string | number;
 
   /**
-   * 属性录入方式(manual=手工录入,select=从列表中选择)
+   * 属性录入方式(1=手工录入,2=从列表中选择)
    */
   entryMethod?: string;
 
@@ -100,7 +105,7 @@ export interface AttributesForm extends BaseEntity {
   /**
    * 是否必填: 1=是, 0=否
    */
-  required?: string;
+  required?: string | number;
 
   /**
    * 备注

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

@@ -49,6 +49,11 @@ export interface TaxCodeVO {
    */
   remark: string;
 
+  /**
+   * 是否有下级 0不存在 1存在
+   */
+  isBottom: string;
+
 }
 
 export interface TaxCodeForm extends BaseEntity {

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

@@ -170,7 +170,8 @@ const getList = async () => {
   loading.value = true;
   try {
     const res = await listTaxCode(queryParams.value);
-    listData.value = res.rows ?? [];
+    // 只展示无下级(isBottom === '0')的叶子节点
+    listData.value = (res.rows ?? []).filter((row: TaxCodeVO) => row.isBottom === '0');
     total.value = res.total ?? 0;
   } finally {
     loading.value = false;
@@ -203,6 +204,7 @@ const handleSearch = () => {
 
 /** 双击行选择 */
 const handleRowDblClick = (row: TaxCodeVO) => {
+  if (row.isBottom !== '0') return;
   emit('select', row);
   dialog.closeDialog();
 };

+ 2 - 1
src/components/upload-image/index.vue

@@ -12,7 +12,8 @@
               <el-image class="w-full h-full" :src="imagesData[0].indexOf('data:image') != -1 ? imagesData[0] : img(imagesData[0])"></el-image>
             </div>
             <div class="absolute z-[1] flex items-center justify-center w-full h-full inset-0 bg-black bg-opacity-60 operation">
-              <icon name="element ZoomIn" color="#fff" size="18px" class="mr-[10px]" @click="previewImage(imagesData, 0)" />
+              <icon name="element Edit" color="#fff" size="18px" class="mr-[10px]" @click.stop="openDialog" />
+              <icon name="element ZoomIn" color="#fff" size="18px" class="mr-[10px]" @click.stop="previewImage(imagesData, 0)" />
               <icon name="element Delete" color="#fff" size="18px" @click.stop="removeImage" />
             </div>
           </div>

+ 58 - 16
src/views/product/attributes/index.vue

@@ -55,21 +55,13 @@
             <span>{{ getCategoryFullPath(scope.row.categoryId) }}</span>
           </template>
         </el-table-column>
-        <el-table-column label="属性是否可选" align="center" prop="isOptional" width="120">
-          <template #default="scope">
-            <dict-tag :options="[
-              { label: '是', value: '1' },
-              { label: '否', value: '0' }
-            ]" :value="String(scope.row.isOptional)" />
-          </template>
-        </el-table-column>
-        <el-table-column label="属性值的输入方式" align="center" prop="entryMethod" width="140">
+        <el-table-column label="属性值的输入方式" align="center" prop="isOptional" width="140">
           <template #default="scope">
             <dict-tag :options="[
               { label: '唯一属性', value: '1' },
               { label: '单选属性', value: '0' },
               { label: '复选属性', value: '2' }
-            ]" :value="String(scope.row.entryMethod)" />
+            ]" :value="String(scope.row.isOptional)" />
           </template>
         </el-table-column>
         <el-table-column label="可选值列表" align="center" prop="attributesList" show-overflow-tooltip />
@@ -141,12 +133,27 @@
           </el-radio-group>
         </el-form-item>
         <el-form-item label="可选值列表" prop="attributesList" v-if="form.entryMethod === '2'">
-          <el-input 
-            v-model="form.attributesList" 
-            type="textarea" 
-            :rows="4" 
-            placeholder="请输入属性值,各属性值请用逗号隔开并来示意。例如:A4,70g,100g" 
-          />
+          <div class="attributes-tag-input w-full">
+            <div class="tag-list border rounded px-2 py-1 min-h-[36px] flex flex-wrap gap-1 cursor-text" @click="focusTagInput">
+              <el-tag
+                v-for="(tag, index) in attributeTagList"
+                :key="index"
+                closable
+                size="small"
+                @close="removeTag(index)"
+              >{{ tag }}</el-tag>
+              <input
+                ref="tagInputRef"
+                v-model="tagInputValue"
+                class="tag-input border-none outline-none text-sm min-w-[80px] flex-1"
+                placeholder="输入后按回车或逗号添加"
+                @keydown.enter.prevent="addTag"
+                @keydown.188.prevent="addTag"
+                @blur="addTag"
+              />
+            </div>
+            <div class="text-gray-400 text-xs mt-1">每个值输入后按回车键或英文逗号确认,例如:50g,70g,100g</div>
+          </div>
         </el-form-item>
       </el-form>
       <template #footer>
@@ -180,6 +187,41 @@ const categoryOptions = ref<categoryTreeVO[]>([]);
 
 const queryFormRef = ref<ElFormInstance>();
 const attributesFormRef = ref<ElFormInstance>();
+const tagInputRef = ref<HTMLInputElement>();
+const tagInputValue = ref('');
+
+/** tag列表(从 form.attributesList 中解析) */
+const attributeTagList = computed({
+  get: () => {
+    return form.value.attributesList
+      ? form.value.attributesList.split(',').map(s => s.trim()).filter(Boolean)
+      : [];
+  },
+  set: (tags: string[]) => {
+    form.value.attributesList = tags.join(',');
+  }
+});
+
+/** 聚焦输入框 */
+const focusTagInput = () => {
+  tagInputRef.value?.focus();
+};
+
+/** 添加 tag */
+const addTag = () => {
+  const val = tagInputValue.value.trim().replace(/,$/,'');
+  if (val && !attributeTagList.value.includes(val)) {
+    attributeTagList.value = [...attributeTagList.value, val];
+  }
+  tagInputValue.value = '';
+};
+
+/** 删除 tag */
+const removeTag = (index: number) => {
+  const list = [...attributeTagList.value];
+  list.splice(index, 1);
+  attributeTagList.value = list;
+};
 
 const dialog = reactive<DialogOption>({
   visible: false,

+ 35 - 29
src/views/product/base/add.vue

@@ -158,10 +158,10 @@
             </el-form-item>
 
             <!-- 商品编号 -->
-            <el-row :gutter="20">
+            <el-row :gutter="20" v-if="route.params.id">
               <el-col :span="12">
                 <el-form-item label="商品编号:" prop="productNo">
-                  <el-input v-model="productForm.productNo" placeholder="002169745" maxlength="20" show-word-limit disabled />
+                  <el-input v-model="productForm.productNo" maxlength="20" show-word-limit disabled />
                 </el-form-item>
               </el-col>
               <el-col :span="12">
@@ -229,7 +229,7 @@
                     :loading="brandLoading"
                     class="w-full"
                   >
-                    <el-option v-for="item in brandOptions" :key="item.id" :label="item.brandName" :value="item.id" />
+                    <el-option v-for="item in brandOptions" :key="item.id" :label="`${item.brandNo},${item.brandName}`" :value="item.id" />
                   </el-select>
                 </el-form-item>
               </el-col>
@@ -243,7 +243,7 @@
                     class="w-full"
                     :disabled="productForm.productReviewStatus === 1"
                   >
-                    <el-option v-for="option in unitOptions" :key="option.id" :label="option.unitName" :value="option.id" />
+                    <el-option v-for="option in unitOptions" :key="option.id" :label="`${option.unitNo},${option.unitName}`" :value="option.id" />
                   </el-select>
                 </el-form-item>
               </el-col>
@@ -270,7 +270,7 @@
               <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.taxrate" :value="option.taxrate" />
+                    <el-option v-for="option in taxRateOptions" :key="option.id" :label="`${option.taxrateNo},${option.taxrateName}`" :value="option.taxrate" />
                   </el-select>
                 </el-form-item>
               </el-col>
@@ -346,7 +346,6 @@
               <el-input v-model="productForm.referenceLink" type="textarea" :rows="3" placeholder="请输入参考链接" />
             </el-form-item>
 
-
             <!-- 主供应商 -->
             <el-form-item label="主供应商:" prop="mainLibraryIntro" required>
               <el-select v-model="productForm.mainLibraryIntro" placeholder="请选择" clearable class="w-full" value-key="id">
@@ -392,13 +391,7 @@
             <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 v-model="productForm.marketPrice" type="number" placeholder="请输入市场价" :min="0" @blur="formatPrice('marketPrice')" />
                 </el-form-item>
               </el-col>
               <el-col :span="8">
@@ -427,7 +420,7 @@
             <el-row :gutter="20">
               <el-col :span="8">
                 <el-form-item label="最低起订量:" prop="minOrderQuantity" required>
-                  <el-input v-model="productForm.minOrderQuantity" type="number" placeholder="请输入最低起订量" />
+                  <el-input v-model="productForm.minOrderQuantity" min="1" type="number" placeholder="请输入最低起订量" />
                 </el-form-item>
               </el-col>
               <el-col :span="8">
@@ -553,7 +546,7 @@
                     >
                       <!-- 下拉选择 -->
                       <el-select
-                        v-if="attributesList[rowIndex * 2 + colIndex - 1].entryMethod === '1'"
+                        v-if="attributesList[rowIndex * 2 + colIndex - 1].isOptional === '1'"
                         v-model="productAttributesValues[attributesList[rowIndex * 2 + colIndex - 1].id]"
                         placeholder="请选择"
                         clearable
@@ -568,7 +561,7 @@
                       </el-select>
                       <!-- 多选 -->
                       <el-select
-                        v-else-if="attributesList[rowIndex * 2 + colIndex - 1].entryMethod === '3'"
+                        v-else-if="attributesList[rowIndex * 2 + colIndex - 1].isOptional === '2'"
                         v-model="productAttributesValues[attributesList[rowIndex * 2 + colIndex - 1].id]"
                         placeholder="请选择"
                         multiple
@@ -605,19 +598,19 @@
 
           <el-form ref="detailFormRef" :model="productForm" label-width="120px" class="product-info-form">
             <!-- 商品主图 -->
-            <el-form-item label="商品主图:">
+            <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>
             </el-form-item>
 
             <!-- 商品轮播图 -->
-            <el-form-item label="商品轮播图:">
+            <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>
             </el-form-item>
 
             <!-- 商品详情 -->
-            <el-form-item label="商品详情:">
+            <el-form-item label="商品详情:" required>
               <el-tabs v-model="activeDetailTab" type="border-card">
                 <el-tab-pane label="电脑端详情" name="pc">
                   <Editor v-model="productForm.pcDetail" :height="400" />
@@ -780,11 +773,7 @@ import {
   getUnitList,
   getTaxRateList
 } from '@/api/product/base';
-import {
-  addBaseAudit,
-  updateBaseAudit,
-  getBaseAudit,
-} from '@/api/product/baseAudit';
+import { addBaseAudit, updateBaseAudit, getBaseAudit } from '@/api/product/baseAudit';
 import { BaseAuditVO, BaseAuditQuery, BaseAuditForm } from '@/api/product/baseAudit/types';
 import { getTaxCode } from '@/api/system/taxCode';
 import { listBrand, getBrand } from '@/api/product/brand';
@@ -825,13 +814,13 @@ const handleTaxCodeSelect = async (row: any) => {
   try {
     const taxRes = await getTaxCode(row.id);
     if (taxRes.data) {
-      taxCodeNo.value = taxRes.data.name || row.name || '';
+      taxCodeNo.value = `${taxRes.data.taxationNo},${taxRes.data.name}`;
     } else {
-      taxCodeNo.value = row.name || '';
+      taxCodeNo.value = row.taxationNo ? `${row.taxationNo},${row.name}` : (row.name || '');
     }
   } catch (e) {
     console.error('获取税率编码详情失败:', e);
-    taxCodeNo.value = row.name || '';
+    taxCodeNo.value = row.taxationNo ? `${row.taxationNo},${row.name}` : (row.name || '');
   }
   // 同时将显示值存入 form,方便编辑回显时直接读取
   (productForm as any).taxationNo = taxCodeNo.value;
@@ -1295,6 +1284,23 @@ const handleSubmit = async () => {
   try {
     submitLoading.value = true;
 
+    // 校验商品主图、轮播图、详情必填
+    if (!productForm.productImage) {
+      ElMessage.warning('请上传商品主图');
+      submitLoading.value = false;
+      return;
+    }
+    if (!carouselImages.value || carouselImages.value.length === 0) {
+      ElMessage.warning('请上传商品轮播图');
+      submitLoading.value = false;
+      return;
+    }
+    if (!productForm.pcDetail) {
+      ElMessage.warning('请填写电脑端商品详情');
+      submitLoading.value = false;
+      return;
+    }
+
     // 校验价格关系:市场价 > 官网价 > 最低售价
     const midRange = parseFloat(String(productForm.marketPrice));
     const standard = parseFloat(String(productForm.memberPrice));
@@ -1338,7 +1344,7 @@ const handleSubmit = async () => {
       auditStatus: 0
     };
     if (productForm.id) {
-      await updateBase(auditData);
+      await addBaseAudit(auditData);
       ElMessage.success('修改成功');
     } else {
       await addBaseAudit(auditData);
@@ -1632,7 +1638,7 @@ const loadProductDetail = async () => {
         try {
           const taxRes = await getTaxCode(rawData.taxationId);
           if (taxRes.data) {
-            taxCodeNo.value = taxRes.data.name || '';
+            taxCodeNo.value = `${taxRes.data.taxationNo},${taxRes.data.name}`;
           }
         } catch (e) {
           console.error('获取税率编码失败:', e);

+ 29 - 51
src/views/product/base/index.vue

@@ -47,16 +47,16 @@
                   </el-select>
                 </el-form-item>
               </el-col>
-              <el-col :span="6">
-                <el-form-item label="审核状态" prop="productReviewStatus">
-                  <el-select v-model="queryParams.productReviewStatus" placeholder="请选择" clearable>
-                    <el-option label="待采购审核" value="0" />
-                    <el-option label="审核通过" value="1" />
-                    <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="productReviewStatus">-->
+<!--                  <el-select v-model="queryParams.productReviewStatus" placeholder="请选择" clearable>-->
+<!--                    <el-option label="待采购审核" value="0" />-->
+<!--                    <el-option label="审核通过" value="1" />-->
+<!--                    <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>
@@ -220,15 +220,15 @@
             <span v-else>-</span>
           </template>
         </el-table-column>
-        <el-table-column label="审核状态" align="center" prop="productReviewStatus" width="90">
-          <template #default="scope">
-            <span v-if="scope.row.productReviewStatus === 0">待采购审核</span>
-            <span v-else-if="scope.row.productReviewStatus === 1">审核通过</span>
-            <span v-else-if="scope.row.productReviewStatus === 2">驳回</span>
-            <span v-else-if="scope.row.productReviewStatus === 3">待营销审核</span>
-            <span v-else>-</span>
-          </template>
-        </el-table-column>
+<!--        <el-table-column label="审核状态" align="center" prop="productReviewStatus" width="90">-->
+<!--          <template #default="scope">-->
+<!--            <span v-if="scope.row.productReviewStatus === 0">待采购审核</span>-->
+<!--            <span v-else-if="scope.row.productReviewStatus === 1">审核通过</span>-->
+<!--            <span v-else-if="scope.row.productReviewStatus === 2">驳回</span>-->
+<!--            <span v-else-if="scope.row.productReviewStatus === 3">待营销审核</span>-->
+<!--            <span v-else>-</span>-->
+<!--          </template>-->
+<!--        </el-table-column>-->
         <el-table-column label="上下架状态" align="center" prop="productStatus" width="100">
           <template #default="scope">
             <el-tag v-if="scope.row.productStatus === 1" type="success">已上架</el-tag>
@@ -239,42 +239,21 @@
         </el-table-column>
         <el-table-column label="操作" align="center" width="150" fixed="right">
           <template #default="scope">
-            <!-- 待审核状态:只显示编辑 -->
-            <div v-if="scope.row.productReviewStatus !== 1" class="flex gap-1 justify-center">
-              <el-link type="primary" :underline="false" @click="handleUpdate(scope.row)">编辑</el-link>
-            </div>
-
-            <!-- 审核通过 -->
-            <div v-else-if="scope.row.productReviewStatus === 1" class="flex flex-col gap-1">
-              <!-- 下架状态:编辑、上架、停售、修改库存 -->
-              <div v-if="scope.row.productStatus === 0" class="flex gap-1 justify-center">
-                <el-link type="primary" :underline="false" @click="handleUpdate(scope.row)">编辑</el-link>
-                <el-link type="success" :underline="false" @click="handleShelf(scope.row)">上架</el-link>
-                <el-link type="danger" :underline="false" @click="handleDiscontinue(scope.row)">停售</el-link>
-              </div>
-
-              <!-- 上架状态:编辑、下架、停售、修改库存 -->
-              <div v-else-if="scope.row.productStatus === 1" class="flex gap-1 justify-center">
-                <el-link type="primary" :underline="false" @click="handleUpdate(scope.row)">编辑</el-link>
-                <el-link type="warning" :underline="false" @click="handleShelf(scope.row)">下架</el-link>
-                <el-link type="danger" :underline="false" @click="handleDiscontinue(scope.row)">停售</el-link>
-              </div>
-              <div v-if="scope.row.productStatus === 1 && scope.row.isSelf === 0" class="flex gap-1 justify-center">
-                <el-link type="info" :underline="false" @click="handleAddToSelfPool(scope.row)">加入自营池</el-link>
-              </div>
-              <!-- 自营商品显示加入精品池按钮 -->
-              <div v-if="scope.row.productStatus === 1 && scope.row.isSelf === 1 && scope.row.productCategory === 1" class="flex gap-1 justify-center">
-                <el-link type="warning" :underline="false" @click="handleAddToExquisitePool(scope.row)">加入精品池</el-link>
-              </div>
-            </div>
-
             <!-- 其他状态(待提交、审核驳回等):显示编辑 -->
-            <div v-else class="flex gap-1 justify-center">
+            <div class="flex gap-1 justify-center">
               <el-link type="primary" :underline="false" @click="handleUpdate(scope.row)">编辑</el-link>
             </div>
             <div class="flex gap-1 justify-center">
               <el-link type="primary" :underline="false" @click="handleSupply(scope.row)">修改库存</el-link>
             </div>
+            <div v-if="scope.row.productStatus === 0" class="flex gap-1 justify-center">
+              <el-link type="success" :underline="false" @click="handleShelf(scope.row)">上架</el-link>
+              <el-link type="danger" :underline="false" @click="handleDiscontinue(scope.row)">停售</el-link>
+            </div>
+            <div v-if="scope.row.productStatus === 1" class="flex gap-1 justify-center">
+              <el-link type="warning" :underline="false" @click="handleShelf(scope.row)">下架</el-link>
+              <el-link type="danger" :underline="false" @click="handleDiscontinue(scope.row)">停售</el-link>
+            </div>
           </template>
         </el-table-column>
       </el-table>
@@ -427,7 +406,6 @@ const data = reactive<PageData<BaseForm, BaseQuery>>({
     bottomCategoryId: [{ required: true, message: '底层分类id不能为空', trigger: 'blur' }],
     unitId: [{ required: true, message: '单位id不能为空', trigger: 'blur' }],
     productImage: [{ required: true, message: '产品图片URL不能为空', trigger: 'blur' }],
-    productReviewStatus: [{ required: true, message: '产品审核状态 0=待采购审核,1=审核通过,2=驳回,3=待营销审核不能为空', trigger: 'change' }],
     homeRecommended: [{ required: true, message: '首页推荐:1=推荐,0=不推荐不能为空', trigger: 'blur' }],
     categoryRecommendation: [{ required: true, message: '分类推荐:1=推荐,0=不推荐不能为空', trigger: 'blur' }],
     cartRecommendation: [{ required: true, message: '购物车推荐:1=推荐,0=不推荐不能为空', trigger: 'blur' }],
@@ -639,7 +617,7 @@ const handleExport = async () => {
 
 /** 查看商品详情 */
 const handleView = (row: BaseVO) => {
-  const url = `https://item.xiaoluwebsite.xyz/item?id=${row.id}`;
+  const url = `https://item.xiaoluwebsite.xyz/item?productNo=${row.productNo}`;
   window.open(url, '_blank');
 };
 

+ 29 - 12
src/views/product/baseAudit/add.vue

@@ -229,7 +229,7 @@
                     :loading="brandLoading"
                     class="w-full"
                   >
-                    <el-option v-for="item in brandOptions" :key="item.id" :label="item.brandName" :value="item.id" />
+                    <el-option v-for="item in brandOptions" :key="item.id" :label="`${item.brandNo},${item.brandName}`" :value="item.id" />
                   </el-select>
                 </el-form-item>
               </el-col>
@@ -243,7 +243,7 @@
                     class="w-full"
                     :disabled="productForm.productReviewStatus === 1"
                   >
-                    <el-option v-for="option in unitOptions" :key="option.id" :label="option.unitName" :value="option.id" />
+                    <el-option v-for="option in unitOptions" :key="option.id" :label="`${option.unitNo},${option.unitName}`" :value="option.id" />
                   </el-select>
                 </el-form-item>
               </el-col>
@@ -270,7 +270,7 @@
               <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.taxrate" :value="option.taxrate" />
+                    <el-option v-for="option in taxRateOptions" :key="option.id" :label="`${option.taxrateNo},${option.taxrateName}`" :value="option.taxrate" />
                   </el-select>
                 </el-form-item>
               </el-col>
@@ -553,7 +553,7 @@
                     >
                       <!-- 下拉选择 -->
                       <el-select
-                        v-if="attributesList[rowIndex * 2 + colIndex - 1].entryMethod === '1'"
+                        v-if="attributesList[rowIndex * 2 + colIndex - 1].isOptional === '1'"
                         v-model="productAttributesValues[attributesList[rowIndex * 2 + colIndex - 1].id]"
                         placeholder="请选择"
                         clearable
@@ -568,7 +568,7 @@
                       </el-select>
                       <!-- 多选 -->
                       <el-select
-                        v-else-if="attributesList[rowIndex * 2 + colIndex - 1].entryMethod === '3'"
+                        v-else-if="attributesList[rowIndex * 2 + colIndex - 1].isOptional === '2'"
                         v-model="productAttributesValues[attributesList[rowIndex * 2 + colIndex - 1].id]"
                         placeholder="请选择"
                         multiple
@@ -605,19 +605,19 @@
 
           <el-form ref="detailFormRef" :model="productForm" label-width="120px" class="product-info-form">
             <!-- 商品主图 -->
-            <el-form-item label="商品主图:">
+            <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>
             </el-form-item>
 
             <!-- 商品轮播图 -->
-            <el-form-item label="商品轮播图:">
+            <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>
             </el-form-item>
 
             <!-- 商品详情 -->
-            <el-form-item label="商品详情:">
+            <el-form-item label="商品详情:" required>
               <el-tabs v-model="activeDetailTab" type="border-card">
                 <el-tab-pane label="电脑端详情" name="pc">
                   <Editor v-model="productForm.pcDetail" :height="400" />
@@ -825,13 +825,13 @@ const handleTaxCodeSelect = async (row: any) => {
   try {
     const taxRes = await getTaxCode(row.id);
     if (taxRes.data) {
-      taxCodeNo.value = taxRes.data.name || row.name || '';
+      taxCodeNo.value = `${taxRes.data.taxationNo},${taxRes.data.name}`;
     } else {
-      taxCodeNo.value = row.name || '';
+      taxCodeNo.value = row.taxationNo ? `${row.taxationNo},${row.name}` : (row.name || '');
     }
   } catch (e) {
     console.error('获取税率编码详情失败:', e);
-    taxCodeNo.value = row.name || '';
+    taxCodeNo.value = row.taxationNo ? `${row.taxationNo},${row.name}` : (row.name || '');
   }
   // 同时将显示值存入 form,方便编辑回显时直接读取
   (productForm as any).taxationNo = taxCodeNo.value;
@@ -1295,6 +1295,23 @@ const handleSubmit = async () => {
   try {
     submitLoading.value = true;
 
+    // 校验商品主图、轮播图、详情必填
+    if (!productForm.productImage) {
+      ElMessage.warning('请上传商品主图');
+      submitLoading.value = false;
+      return;
+    }
+    if (!carouselImages.value || carouselImages.value.length === 0) {
+      ElMessage.warning('请上传商品轮播图');
+      submitLoading.value = false;
+      return;
+    }
+    if (!productForm.pcDetail) {
+      ElMessage.warning('请填写电脑端商品详情');
+      submitLoading.value = false;
+      return;
+    }
+
     // 校验价格关系:市场价 > 官网价 > 最低售价
     const midRange = parseFloat(String(productForm.marketPrice));
     const standard = parseFloat(String(productForm.memberPrice));
@@ -1633,7 +1650,7 @@ const loadProductDetail = async () => {
         try {
           const taxRes = await getTaxCode(rawData.taxationId);
           if (taxRes.data) {
-            taxCodeNo.value = taxRes.data.name || '';
+            taxCodeNo.value = `${taxRes.data.taxationNo},${taxRes.data.name}`;
           }
         } catch (e) {
           console.error('获取税率编码失败:', e);

+ 0 - 22
src/views/product/baseAudit/index.vue

@@ -47,16 +47,6 @@
                   </el-select>
                 </el-form-item>
               </el-col>
-              <el-col :span="6">
-                <el-form-item label="审核状态" prop="productReviewStatus">
-                  <el-select v-model="queryParams.productReviewStatus" placeholder="请选择" clearable>
-                    <el-option label="待采购审核" value="0" />
-                    <el-option label="审核通过" value="1" />
-                    <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>
@@ -195,17 +185,6 @@
 <!--            <span>{{ scope.row.productBaseVo.dataSource || '-' }}</span>-->
 <!--          </template>-->
 <!--        </el-table-column>-->
-        <el-table-column label="审核状态" align="center" prop="productReviewStatus" width="90">
-          <template #default="scope">
-            <span v-if="scope.row.auditStatus === 0">待提交</span>
-            <span v-else-if="scope.row.auditStatus === 1">待审核</span>
-            <span v-else-if="scope.row.auditStatus === 2">审核通过</span>
-            <span v-else-if="scope.row.auditStatus === 3">审核驳回</span>
-            <span v-else-if="scope.row.auditStatus === 4">待审核</span>
-            <span v-else-if="scope.row.auditStatus === 5">待审核</span>
-            <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>
@@ -415,7 +394,6 @@ const data = reactive<PageData<BaseForm, BaseQuery>>({
     bottomCategoryId: [{ required: true, message: '底层分类id不能为空', trigger: 'blur' }],
     unitId: [{ required: true, message: '单位id不能为空', trigger: 'blur' }],
     productImage: [{ required: true, message: '产品图片URL不能为空', trigger: 'blur' }],
-    productReviewStatus: [{ required: true, message: '产品审核状态 0=待采购审核,1=审核通过,2=驳回,3=待营销审核不能为空', trigger: 'change' }],
     homeRecommended: [{ required: true, message: '首页推荐:1=推荐,0=不推荐不能为空', trigger: 'blur' }],
     categoryRecommendation: [{ required: true, message: '分类推荐:1=推荐,0=不推荐不能为空', trigger: 'blur' }],
     cartRecommendation: [{ required: true, message: '购物车推荐:1=推荐,0=不推荐不能为空', trigger: 'blur' }],

+ 0 - 5
src/views/product/category/index.vue

@@ -44,11 +44,6 @@
           </template>
         </el-table-column>
         <el-table-column prop="sort" align="center" label="排序" width="150"></el-table-column>
-        <el-table-column prop="platform" align="center" label="平台" width="150">
-          <template #default="scope">
-            <span>{{ scope.row.platform === 0 ? '工业品' : scope.row.platform === 1 ? 'PC端' : '未知' }}</span>
-          </template>
-        </el-table-column>
         <el-table-column prop="isShow" align="center" label="是否提示" width="150">
           <template #default="scope">
             <span>{{ scope.row.isShow === 1 ? '是' : '否' }}</span>

+ 152 - 79
src/views/product/poolAudit/ProductDetailDrawer.vue

@@ -4,81 +4,83 @@
       <!-- 产品基础信息 -->
       <div class="section-title">产品基础信息</div>
 
-      <el-descriptions :column="2" border class="mb-4">
-        <el-descriptions-item label="产品编号">{{ baseInfo?.productNo || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="产品名称">{{ baseInfo?.itemName || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="商品类型">
-          <el-tag v-if="(baseInfo as any)?.productCategory === 1" type="info">默认类型</el-tag>
-          <el-tag v-else-if="(baseInfo as any)?.productCategory === 2" type="success">精选商品</el-tag>
-          <el-tag v-else-if="(baseInfo as any)?.productCategory === 3" type="danger">停售商品</el-tag>
-          <span v-else>-</span>
-        </el-descriptions-item>
-        <el-descriptions-item label="产品类别">
-          {{ [(baseInfo as any)?.topCategoryName, (baseInfo as any)?.mediumCategoryName, (baseInfo as any)?.bottomCategoryName].filter(Boolean).join(' / ') || '-' }}
-        </el-descriptions-item>
-        <el-descriptions-item label="品牌">{{ (baseInfo as any)?.brandName || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="单位">{{ (baseInfo as any)?.unitName || '-' }}</el-descriptions-item>
-      </el-descriptions>
+      <div class="info-grid mb-4">
+        <div class="info-item"><span class="info-label">产品编号:</span><span class="info-value">{{ baseInfo?.productNo || '-' }}</span></div>
+        <div class="info-item"><span class="info-label">产品名称:</span><span class="info-value">{{ baseInfo?.itemName || '-' }}</span></div>
+        <div class="info-item">
+          <span class="info-label">商品类型:</span>
+          <span class="info-value">
+            <el-tag v-if="(baseInfo as any)?.productCategory === 1" type="info">默认类型</el-tag>
+            <el-tag v-else-if="(baseInfo as any)?.productCategory === 2" type="success">精选商品</el-tag>
+            <el-tag v-else-if="(baseInfo as any)?.productCategory === 3" type="danger">停售商品</el-tag>
+            <span v-else>-</span>
+          </span>
+        </div>
+        <div class="info-item"><span class="info-label">产品类别:</span><span class="info-value">{{ [(baseInfo as any)?.topCategoryName, (baseInfo as any)?.mediumCategoryName, (baseInfo as any)?.bottomCategoryName].filter(Boolean).join(' / ') || '-' }}</span></div>
+        <div class="info-item"><span class="info-label">品牌:</span><span class="info-value">{{ (baseInfo as any)?.brandName || '-' }}</span></div>
+        <div class="info-item"><span class="info-label">单位:</span><span class="info-value">{{ (baseInfo as any)?.unitName || '-' }}</span></div>
+      </div>
 
       <!-- 价格信息 -->
       <div class="sub-title">价格信息</div>
-      <el-descriptions :column="3" border class="mb-4">
-        <el-descriptions-item label="市场价">
-          <span class="text-red-500">¥{{ baseInfo?.marketPrice ?? '-' }}</span>
-        </el-descriptions-item>
-        <el-descriptions-item label="官网价">
-          <span class="text-red-500">¥{{ baseInfo?.memberPrice ?? '-' }}</span>
-        </el-descriptions-item>
-        <el-descriptions-item label="最低销售价格">
-          <span class="text-red-500">¥{{ baseInfo?.minSellingPrice ?? '-' }}</span>
-        </el-descriptions-item>
-      </el-descriptions>
+      <div class="info-grid mb-4">
+        <div class="info-item"><span class="info-label">市场价:</span><span class="info-value text-red-500">¥{{ baseInfo?.marketPrice ?? '-' }}</span></div>
+        <div class="info-item"><span class="info-label">官网价:</span><span class="info-value text-red-500">¥{{ baseInfo?.memberPrice ?? '-' }}</span></div>
+        <div class="info-item"><span class="info-label">最低销售价格:</span><span class="info-value text-red-500">¥{{ baseInfo?.minSellingPrice ?? '-' }}</span></div>
+      </div>
 
       <!-- 采购信息 -->
       <div class="sub-title">采购信息</div>
-      <el-descriptions :column="3" border class="mb-4">
-        <el-descriptions-item label="采购价格">
-          <span>¥{{ baseInfo?.purchasingPrice ?? '-' }}</span>
-        </el-descriptions-item>
-        <el-descriptions-item label="暂估毛利率">
-          <span>{{ baseInfo?.tempGrossMargin != null ? baseInfo.tempGrossMargin + '%' : '-' }}</span>
-        </el-descriptions-item>
-        <el-descriptions-item label="最高采购价">
-          <span>¥{{ (baseInfo as any)?.maxPurchasePrice ?? '-' }}</span>
-        </el-descriptions-item>
-      </el-descriptions>
+      <div class="info-grid mb-4">
+        <div class="info-item"><span class="info-label">采购价格:</span><span class="info-value">¥{{ baseInfo?.purchasingPrice ?? '-' }}</span></div>
+        <div class="info-item"><span class="info-label">暂估毛利率:</span><span class="info-value">{{ baseInfo?.memberPrice ? (((baseInfo.memberPrice - (baseInfo.purchasingPrice || 0)) / baseInfo.memberPrice) * 100).toFixed(2) + '%' : '-' }}</span></div>
+        <div class="info-item"><span class="info-label">最高采购价:</span><span class="info-value">¥{{ (baseInfo as any)?.maxPurchasePrice ?? '-' }}</span></div>
+      </div>
 
       <!-- 其他基础信息 -->
-      <el-descriptions :column="3" border class="mb-4">
-        <el-descriptions-item label="起订量">{{ baseInfo?.minOrderQuantity ?? '-' }}</el-descriptions-item>
-        <el-descriptions-item label="上下架状态">
-          <el-tag v-if="baseInfo?.productStatus === 1" type="success">已上架</el-tag>
-          <el-tag v-else-if="baseInfo?.productStatus === 0" type="warning">下架</el-tag>
-          <el-tag v-else-if="baseInfo?.productStatus === 2" type="info">上架中</el-tag>
-          <span v-else>-</span>
-        </el-descriptions-item>
-        <el-descriptions-item label="主供应商">{{ (baseInfo as any)?.freightPrice ?? '-' }}</el-descriptions-item>
-      </el-descriptions>
+      <div class="info-grid mb-4">
+        <div class="info-item"><span class="info-label">起订量:</span><span class="info-value">{{ baseInfo?.minOrderQuantity ?? '-' }}</span></div>
+        <div class="info-item">
+          <span class="info-label">上下架状态:</span>
+          <span class="info-value">
+            <el-tag v-if="baseInfo?.productStatus === 1" type="success">已上架</el-tag>
+            <el-tag v-else-if="baseInfo?.productStatus === 0" type="warning">下架</el-tag>
+            <el-tag v-else-if="baseInfo?.productStatus === 2" type="info">上架中</el-tag>
+            <span v-else>-</span>
+          </span>
+        </div>
+        <div class="info-item"><span class="info-label">主供应商:</span><span class="info-value">{{ (baseInfo as any)?.freightPrice ?? '-' }}</span></div>
+      </div>
 
       <el-divider />
 
       <!-- 入池信息 -->
       <div class="section-title">入池信息</div>
-      <el-descriptions :column="2" border class="mb-4">
-        <el-descriptions-item label="申请类型" :span="2">
-          <el-tag v-if="String(auditInfo?.type) === '0'" type="success">入池申请</el-tag>
-          <el-tag v-else-if="String(auditInfo?.type) === '1'" type="warning">出池申请</el-tag>
-          <span v-else>-</span>
-        </el-descriptions-item>
-        <el-descriptions-item label="申请单号">{{ auditInfo?.name || auditInfo?.id || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="申请人">{{ auditInfo?.createByName || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="申请时间">{{ (auditInfo as any)?.createTime || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="备注" :span="2">{{ auditInfo?.remark || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="附件" :span="2">
-          <el-tag v-if="(auditInfo as any)?.attachment" type="success">已上传</el-tag>
-          <el-tag v-else type="info">暂无附件</el-tag>
-        </el-descriptions-item>
-      </el-descriptions>
+      <div class="info-grid mb-4">
+        <div class="info-item info-item--full">
+          <span class="info-label">申请类型:</span>
+          <span class="info-value">
+            <el-tag v-if="String(auditInfo?.type) === '0'" type="success">入池申请</el-tag>
+            <el-tag v-else-if="String(auditInfo?.type) === '1'" type="warning">出池申请</el-tag>
+            <span v-else>-</span>
+          </span>
+        </div>
+        <div class="info-item"><span class="info-label">申请单号:</span><span class="info-value">{{ auditInfo?.name || auditInfo?.id || '-' }}</span></div>
+        <div class="info-item"><span class="info-label">申请人:</span><span class="info-value">{{ auditInfo?.createByName || '-' }}</span></div>
+        <div class="info-item"><span class="info-label">申请时间:</span><span class="info-value">{{ (auditInfo as any)?.createTime || '-' }}</span></div>
+        <div class="info-item info-item--full"><span class="info-label">备注:</span><span class="info-value">{{ auditInfo?.remark || '-' }}</span></div>
+        <div class="info-item info-item--full">
+          <span class="info-label">附件:</span>
+          <span class="info-value">
+            <template v-if="attachmentFiles.length > 0">
+              <div v-for="file in attachmentFiles" :key="String(file.ossId)" class="mb-1">
+                <el-link type="primary" :underline="true" @click="proxy?.$download.oss(file.ossId)">{{ file.fileName }}</el-link>
+              </div>
+            </template>
+            <span v-else>暂无附件</span>
+          </span>
+        </div>
+      </div>
 
       
       <el-divider />
@@ -86,31 +88,43 @@
       <!-- 客户信息(协议池 type=2) -->
       <template v-if="auditInfo && String(auditInfo.type) === '2'">
         <div class="section-title">客户协议信息</div>
-        <el-descriptions :column="2" border class="mb-4" v-loading="extraLoading">
-          <el-descriptions-item label="客户编号">{{ customerInfo?.customerNo || '-' }}</el-descriptions-item>
-          <el-descriptions-item label="客户名称">{{ customerInfo?.customerName || '-' }}</el-descriptions-item>
-          <el-descriptions-item label="协议价格">{{ priceForm.agreementPrice || '-' }}</el-descriptions-item>
-        </el-descriptions>
+        <div class="info-grid mb-4" v-loading="extraLoading">
+          <div class="info-item"><span class="info-label">客户编号:</span><span class="info-value">{{ customerInfo?.customerNo || '-' }}</span></div>
+          <div class="info-item"><span class="info-label">客户名称:</span><span class="info-value">{{ customerInfo?.customerName || '-' }}</span></div>
+          <div class="info-item"><span class="info-label">协议价格:</span><span class="info-value">{{ priceForm.agreementPrice || '-' }}</span></div>
+        </div>
       </template>
 
       <!-- 项目信息(项目池 type=3) -->
       <template v-if="auditInfo && String(auditInfo.type) === '3'">
         <div class="section-title">项目信息</div>
-        <el-descriptions :column="2" border class="mb-4" v-loading="extraLoading">
-          <el-descriptions-item label="项目名称">{{ itemInfo?.itemName || '-' }}</el-descriptions-item>
-          <el-descriptions-item label="项目编号">{{ itemInfo?.itemKey || '-' }}</el-descriptions-item>
-          <el-descriptions-item label="第三方价格">{{ priceForm.agreementPrice || '-' }}</el-descriptions-item>
-        </el-descriptions>
+        <div class="info-grid mb-4" v-loading="extraLoading">
+          <div class="info-item"><span class="info-label">项目名称:</span><span class="info-value">{{ itemInfo?.itemName || '-' }}</span></div>
+          <div class="info-item"><span class="info-label">项目编号:</span><span class="info-value">{{ itemInfo?.itemKey || '-' }}</span></div>
+          <div class="info-item"><span class="info-label">第三方价格:</span><span class="info-value">{{ priceForm.agreementPrice || '-' }}</span></div>
+          <div class="info-item">
+            <span class="info-label">计价规则:</span>
+            <span class="info-value">
+              <el-tag v-if="String(linkInfo?.pricingRule) === '0'" type="primary">一品一价</el-tag>
+              <el-tag v-else-if="String(linkInfo?.pricingRule) === '1'" type="warning">按类目折扣率报价</el-tag>
+              <span v-else>-</span>
+            </span>
+          </div>
+          <div class="info-item">
+            <span class="info-label">第三方分类:</span>
+            <span class="info-value">{{ thirdCategory?.categoryName || '-' }}</span>
+          </div>
+        </div>
       </template>
 
       <!-- 营销产品池信息(营销池 type=4) -->
       <template v-if="auditInfo && String(auditInfo.type) === '4'">
         <div class="section-title">营销产品池信息</div>
-        <el-descriptions :column="2" border class="mb-4" v-loading="extraLoading">
-          <el-descriptions-item label="池编码">{{ poolInfo?.poolNo || '-' }}</el-descriptions-item>
-          <el-descriptions-item label="池名称">{{ poolInfo?.name || '-' }}</el-descriptions-item>
-          <el-descriptions-item label="协议价格">{{ priceForm.agreementPrice || '-' }}</el-descriptions-item>
-        </el-descriptions>
+        <div class="info-grid mb-4" v-loading="extraLoading">
+          <div class="info-item"><span class="info-label">池编码:</span><span class="info-value">{{ poolInfo?.poolNo || '-' }}</span></div>
+          <div class="info-item"><span class="info-label">池名称:</span><span class="info-value">{{ poolInfo?.name || '-' }}</span></div>
+          <!-- <div class="info-item"><span class="info-label">协议价格:</span><span class="info-value">{{ priceForm.agreementPrice || '-' }}</span></div> -->
+        </div>
       </template>
     </div>
 
@@ -126,7 +140,11 @@ import { getPoolAudit } from '@/api/product/poolAudit';
 import { getPoolLinkAudit } from '@/api/product/poolLinkAudit';
 import { getCustomerInfo } from '@/api/customer/customerInfo';
 import { getItem } from '@/api/external/item';
+import { getProductCategory } from '@/api/external/productCategory';
+import { ProductCategoryVO } from '@/api/external/productCategory/types';
 import { getPool } from '@/api/product/pool';
+import { listByIds } from '@/api/system/oss';
+import { OssVO } from '@/api/system/oss/types';
 import { BaseVO } from '@/api/product/base/types';
 import { PoolAuditVO } from '@/api/product/poolAudit/types';
 import { PoolLinkAuditVO } from '@/api/product/poolLinkAudit/types';
@@ -152,14 +170,17 @@ const visible = computed({
   set: (val) => emit('update:modelValue', val)
 });
 
+const { proxy } = getCurrentInstance() as any;
 const loading = ref(false);
 const extraLoading = ref(false);
+const attachmentFiles = ref<OssVO[]>([]);
 const linkInfo = ref<PoolLinkAuditVO | null>(null);
 const baseInfo = ref<BaseVO | null>(null);
 const auditInfo = ref<PoolAuditVO | null>(null);
 const customerInfo = ref<CustomerInfoVO | null>(null);
 const itemInfo = ref<ItemVO | null>(null);
 const poolInfo = ref<PoolVO | null>(null);
+const thirdCategory = ref<ProductCategoryVO | null>(null);
 
 const priceForm = reactive({
   discountRate: undefined as number | undefined,
@@ -176,8 +197,14 @@ const loadExtraInfo = async (type: string, auditData: PoolAuditVO) => {
       customerInfo.value = res.data || null;
     } else if (type === '3' && auditData.itemId) {
       // 项目池 → 查询项目信息
-      const res = await getItem(auditData.itemId);
-      itemInfo.value = res.data || null;
+      const [itemRes, categoryRes] = await Promise.all([
+        getItem(auditData.itemId),
+        linkInfo.value?.categoryId
+          ? getProductCategory(linkInfo.value.categoryId, String(auditData.itemId))
+          : Promise.resolve({ data: null })
+      ]);
+      itemInfo.value = itemRes.data || null;
+      thirdCategory.value = (categoryRes as any)?.data || null;
     } else if (type === '4' && auditData.poolId) {
       // 营销池 → 查询产品池信息
       const res = await getPool(auditData.poolId);
@@ -242,12 +269,30 @@ watch(
       customerInfo.value = null;
       itemInfo.value = null;
       poolInfo.value = null;
+      thirdCategory.value = null;
+      attachmentFiles.value = [];
       priceForm.discountRate = undefined;
       priceForm.agreementPrice = undefined;
       loadData();
     }
   }
 );
+
+watch(
+  () => (auditInfo.value as any)?.attachment,
+  async (val) => {
+    if (val) {
+      try {
+        const res = await listByIds(val);
+        attachmentFiles.value = res.data || [];
+      } catch {
+        attachmentFiles.value = [];
+      }
+    } else {
+      attachmentFiles.value = [];
+    }
+  }
+);
 </script>
 
 <style scoped lang="scss">
@@ -267,4 +312,32 @@ watch(
   margin-bottom: 8px;
   padding-left: 6px;
 }
+
+.info-grid {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 10px 16px;
+}
+
+.info-item {
+  display: flex;
+  align-items: flex-start;
+  font-size: 13px;
+  line-height: 22px;
+
+  &--full {
+    grid-column: 1 / -1;
+  }
+}
+
+.info-label {
+  flex-shrink: 0;
+  color: #909399;
+  min-width: 80px;
+}
+
+.info-value {
+  color: #303133;
+  word-break: break-all;
+}
 </style>

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

@@ -58,17 +58,16 @@
           </template>
         </el-table-column>
         <el-table-column label="创建人" align="center" prop="createByName" />
-        <el-table-column label="审核人" align="center" prop="auditByName" />
+        <!-- <el-table-column label="审核人" align="center" prop="auditByName" /> -->
 
-        <el-table-column label="状态" align="center" prop="productReviewStatus" width="120">
+        <!-- <el-table-column label="状态" align="center" prop="productReviewStatus" width="120">
           <template #default="scope">
             <el-tag :type="reviewStatusTagType(scope.row.productReviewStatus)">
               {{ reviewStatusLabel(scope.row.productReviewStatus) }}
             </el-tag>
           </template>
-        </el-table-column>
+        </el-table-column> -->
         <el-table-column label="申请时间" align="center" prop="createTime" width="180" />
-        <el-table-column label="备注" align="center" prop="remark" width="180" />
         <el-table-column label="操作" align="center" class-name="small-padding fixed-width" min-width="200">
           <template #default="scope">
             <!-- 审核通过或已驳回:只显示查看入池单 -->
@@ -77,7 +76,6 @@
             </template>
             <!-- 其他状态:显示原有操作 -->
             <template v-else>
-              <el-button link type="primary" @click="handleView(scope.row)">查看</el-button>
               <el-button
                 v-if="scope.row.productReviewStatus == '0' || scope.row.productReviewStatus === 0"
                 link type="primary"

+ 87 - 2
src/views/product/poolAudit/itemAudit.vue

@@ -120,6 +120,13 @@
     </el-card>
 
     <el-card shadow="never">
+      <!-- 批量操作工具栏 -->
+      <div  class="mb-2 flex items-center gap-2">
+        <span class="text-sm text-gray-500">已选 <span class="text-blue-600 font-bold">{{ selectedRows.length }}</span> 条</span>
+        <el-button type="success" plain size="small" icon="Check" @click="handleBatchAudit">批量审核通过</el-button>
+        <el-button type="danger" plain size="small" icon="Close" @click="handleBatchReject">批量驳回</el-button>
+      </div>
+
       <el-table v-loading="loading" border :data="baseList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="项目名称" align="center" prop="name" width="100" fixed="left"/>
@@ -290,7 +297,7 @@ import {
   changeProductType,
   getProductStatusCount
 } from '@/api/product/base';
-import { selectPoolAuditProductPage, audit } from '@/api/product/poolAudit';
+import { selectPoolAuditProductPage, audit, batchAudit } from '@/api/product/poolAudit';
 import { ProductListVO } from '@/api/product/poolAudit/types';
 import { generatePPT } from '@/utils/pptPlugin';
 import { addProductSelf } from '@/api/product/productSelf';
@@ -311,6 +318,7 @@ const buttonLoading = ref(false);
 const loading = ref(true);
 const showSearch = ref(true);
 const ids = ref<Array<string | number>>([]);
+const selectedRows = ref<ProductListVO[]>([]);
 const single = ref(true);
 const multiple = ref(true);
 const total = ref(0);
@@ -533,12 +541,89 @@ const resetQuery = () => {
 };
 
 /** 多选框选中数据 */
-const handleSelectionChange = (selection: BaseVO[]) => {
+const handleSelectionChange = (selection: ProductListVO[]) => {
   ids.value = selection.map((item) => item.id);
+  selectedRows.value = selection;
   single.value = selection.length != 1;
   multiple.value = !selection.length;
 };
 
+/** 批量审核通过 */
+const handleBatchAudit = async () => {
+  if (selectedRows.value.length === 0) {
+    proxy?.$modal.msgWarning('请先选择要审核的商品');
+    return;
+  }
+  // 过滤没有 poolAuditId 的行
+  const validRows = selectedRows.value.filter((r) => r.poolAuditId);
+  if (validRows.length === 0) {
+    proxy?.$modal.msgWarning('所选商品均未关联入池单,无法审核');
+    return;
+  }
+  await proxy?.$modal.confirm(`确认批量审核通过所选 ${validRows.length} 个商品吗?`);
+  try {
+    // 按 poolAuditId 分组
+    const groups = new Map<string | number, Array<string | number>>();
+    for (const row of validRows) {
+      const pid = row.poolAuditId!;
+      if (!groups.has(pid)) groups.set(pid, []);
+      groups.get(pid)!.push((row as any).productId ?? row.id);
+    }
+    await Promise.all(
+      Array.from(groups.entries()).map(([poolAuditId, productIds]) =>
+        batchAudit({ poolAuditId, productIds, auditStatus: 2 })
+      )
+    );
+    proxy?.$modal.msgSuccess(`批量审核通过成功(共 ${validRows.length} 条)`);
+    await getList();
+  } catch (error) {
+    console.error('批量审核失败:', error);
+  }
+};
+
+/** 批量驳回 */
+const handleBatchReject = async () => {
+  if (selectedRows.value.length === 0) {
+    proxy?.$modal.msgWarning('请先选择要驳回的商品');
+    return;
+  }
+  const validRows = selectedRows.value.filter((r) => r.poolAuditId);
+  if (validRows.length === 0) {
+    proxy?.$modal.msgWarning('所选商品均未关联入池单,无法驳回');
+    return;
+  }
+  try {
+    const { value: reason } = await ElMessageBox.prompt(
+      `已选 ${validRows.length} 个商品,请输入驳回原因`,
+      '批量驳回',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        inputPlaceholder: '请输入驳回原因',
+        inputValidator: (val) => (val && val.trim() ? true : '驳回原因不能为空')
+      }
+    );
+    // 按 poolAuditId 分组
+    const groups = new Map<string | number, Array<string | number>>();
+    for (const row of validRows) {
+      const pid = row.poolAuditId!;
+      if (!groups.has(pid)) groups.set(pid, []);
+      groups.get(pid)!.push((row as any).productId ?? row.id);
+    }
+    await Promise.all(
+      Array.from(groups.entries()).map(([poolAuditId, productIds]) =>
+        batchAudit({ poolAuditId, productIds, auditStatus: 3, reason: reason.trim() })
+      )
+    );
+    proxy?.$modal.msgSuccess(`批量驳回成功(共 ${validRows.length} 条)`);
+    await getList();
+  } catch (error: any) {
+    if (error !== 'cancel') {
+      console.error('批量驳回失败:', error);
+    }
+  }
+};
+
 /** 新增按钮操作 */
 const handleAdd = () => {
   router.push('/product/base/add');

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

@@ -107,6 +107,13 @@
     </el-card>
 
     <el-card shadow="never">
+      <!-- 批量操作工具栏 -->
+      <div  class="mb-2 flex items-center gap-2">
+        <span class="text-sm text-gray-500">已选 <span class="text-blue-600 font-bold">{{ selectedRows.length }}</span> 条</span>
+        <el-button type="success" plain size="small" icon="Check" @click="handleBatchAudit">批量审核通过</el-button>
+        <el-button type="danger" plain size="small" icon="Close" @click="handleBatchReject">批量驳回</el-button>
+      </div>
+
       <el-table v-loading="loading" border :data="baseList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="入池名称" align="center" prop="name" width="100" fixed="left" />
@@ -275,7 +282,7 @@ import {
   changeProductType,
   getProductStatusCount
 } from '@/api/product/base';
-import { selectPoolAuditProductPage, audit } from '@/api/product/poolAudit';
+import { selectPoolAuditProductPage, audit, batchAudit } from '@/api/product/poolAudit';
 import { ProductListVO } from '@/api/product/poolAudit/types';
 import { generatePPT } from '@/utils/pptPlugin';
 import { addProductSelf } from '@/api/product/productSelf';
@@ -296,6 +303,7 @@ const buttonLoading = ref(false);
 const loading = ref(true);
 const showSearch = ref(true);
 const ids = ref<Array<string | number>>([]);
+const selectedRows = ref<ProductListVO[]>([]);
 const single = ref(true);
 const multiple = ref(true);
 const total = ref(0);
@@ -518,12 +526,89 @@ const resetQuery = () => {
 };
 
 /** 多选框选中数据 */
-const handleSelectionChange = (selection: BaseVO[]) => {
+const handleSelectionChange = (selection: ProductListVO[]) => {
   ids.value = selection.map((item) => item.id);
+  selectedRows.value = selection;
   single.value = selection.length != 1;
   multiple.value = !selection.length;
 };
 
+/** 批量审核通过 */
+const handleBatchAudit = async () => {
+  if (selectedRows.value.length === 0) {
+    proxy?.$modal.msgWarning('请先选择要审核的商品');
+    return;
+  }
+  // 过滤没有 poolAuditId 的行
+  const validRows = selectedRows.value.filter((r) => r.poolAuditId);
+  if (validRows.length === 0) {
+    proxy?.$modal.msgWarning('所选商品均未关联入池单,无法审核');
+    return;
+  }
+  await proxy?.$modal.confirm(`确认批量审核通过所选 ${validRows.length} 个商品吗?`);
+  try {
+    // 按 poolAuditId 分组
+    const groups = new Map<string | number, Array<string | number>>();
+    for (const row of validRows) {
+      const pid = row.poolAuditId!;
+      if (!groups.has(pid)) groups.set(pid, []);
+      groups.get(pid)!.push((row as any).productId ?? row.id);
+    }
+    await Promise.all(
+      Array.from(groups.entries()).map(([poolAuditId, productIds]) =>
+        batchAudit({ poolAuditId, productIds, auditStatus: 2 })
+      )
+    );
+    proxy?.$modal.msgSuccess(`批量审核通过成功(共 ${validRows.length} 条)`);
+    await getList();
+  } catch (error) {
+    console.error('批量审核失败:', error);
+  }
+};
+
+/** 批量驳回 */
+const handleBatchReject = async () => {
+  if (selectedRows.value.length === 0) {
+    proxy?.$modal.msgWarning('请先选择要驳回的商品');
+    return;
+  }
+  const validRows = selectedRows.value.filter((r) => r.poolAuditId);
+  if (validRows.length === 0) {
+    proxy?.$modal.msgWarning('所选商品均未关联入池单,无法驳回');
+    return;
+  }
+  try {
+    const { value: reason } = await ElMessageBox.prompt(
+      `已选 ${validRows.length} 个商品,请输入驳回原因`,
+      '批量驳回',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        inputPlaceholder: '请输入驳回原因',
+        inputValidator: (val) => (val && val.trim() ? true : '驳回原因不能为空')
+      }
+    );
+    // 按 poolAuditId 分组
+    const groups = new Map<string | number, Array<string | number>>();
+    for (const row of validRows) {
+      const pid = row.poolAuditId!;
+      if (!groups.has(pid)) groups.set(pid, []);
+      groups.get(pid)!.push((row as any).productId ?? row.id);
+    }
+    await Promise.all(
+      Array.from(groups.entries()).map(([poolAuditId, productIds]) =>
+        batchAudit({ poolAuditId, productIds, auditStatus: 3, reason: reason.trim() })
+      )
+    );
+    proxy?.$modal.msgSuccess(`批量驳回成功(共 ${validRows.length} 条)`);
+    await getList();
+  } catch (error: any) {
+    if (error !== 'cancel') {
+      console.error('批量驳回失败:', error);
+    }
+  }
+};
+
 /** 新增按钮操作 */
 const handleAdd = () => {
   router.push('/product/base/add');

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

@@ -107,6 +107,13 @@
     </el-card>
 
     <el-card shadow="never">
+      <!-- 批量操作工具栏 -->
+      <div  class="mb-2 flex items-center gap-2">
+        <span class="text-sm text-gray-500">已选 <span class="text-blue-600 font-bold">{{ selectedRows.length }}</span> 条</span>
+        <el-button type="success" plain size="small" icon="Check" @click="handleBatchAudit">批量审核通过</el-button>
+        <el-button type="danger" plain size="small" icon="Close" @click="handleBatchReject">批量驳回</el-button>
+      </div>
+
       <el-table v-loading="loading" border :data="baseList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="客户名称" align="center" prop="name" width="100" fixed="left"/>
@@ -289,7 +296,7 @@ import {
   changeProductType,
   getProductStatusCount
 } from '@/api/product/base';
-import { selectPoolAuditProductPage, audit } from '@/api/product/poolAudit';
+import { selectPoolAuditProductPage, audit, batchAudit } from '@/api/product/poolAudit';
 import { ProductListVO } from '@/api/product/poolAudit/types';
 import { generatePPT } from '@/utils/pptPlugin';
 import { addProductSelf } from '@/api/product/productSelf';
@@ -310,6 +317,7 @@ const buttonLoading = ref(false);
 const loading = ref(true);
 const showSearch = ref(true);
 const ids = ref<Array<string | number>>([]);
+const selectedRows = ref<ProductListVO[]>([]);
 const single = ref(true);
 const multiple = ref(true);
 const total = ref(0);
@@ -532,12 +540,89 @@ const resetQuery = () => {
 };
 
 /** 多选框选中数据 */
-const handleSelectionChange = (selection: BaseVO[]) => {
+const handleSelectionChange = (selection: ProductListVO[]) => {
   ids.value = selection.map((item) => item.id);
+  selectedRows.value = selection;
   single.value = selection.length != 1;
   multiple.value = !selection.length;
 };
 
+/** 批量审核通过 */
+const handleBatchAudit = async () => {
+  if (selectedRows.value.length === 0) {
+    proxy?.$modal.msgWarning('请先选择要审核的商品');
+    return;
+  }
+  // 过滤没有 poolAuditId 的行
+  const validRows = selectedRows.value.filter((r) => r.poolAuditId);
+  if (validRows.length === 0) {
+    proxy?.$modal.msgWarning('所选商品均未关联入池单,无法审核');
+    return;
+  }
+  await proxy?.$modal.confirm(`确认批量审核通过所选 ${validRows.length} 个商品吗?`);
+  try {
+    // 按 poolAuditId 分组
+    const groups = new Map<string | number, Array<string | number>>();
+    for (const row of validRows) {
+      const pid = row.poolAuditId!;
+      if (!groups.has(pid)) groups.set(pid, []);
+      groups.get(pid)!.push((row as any).productId ?? row.id);
+    }
+    await Promise.all(
+      Array.from(groups.entries()).map(([poolAuditId, productIds]) =>
+        batchAudit({ poolAuditId, productIds, auditStatus: 2 })
+      )
+    );
+    proxy?.$modal.msgSuccess(`批量审核通过成功(共 ${validRows.length} 条)`);
+    await getList();
+  } catch (error) {
+    console.error('批量审核失败:', error);
+  }
+};
+
+/** 批量驳回 */
+const handleBatchReject = async () => {
+  if (selectedRows.value.length === 0) {
+    proxy?.$modal.msgWarning('请先选择要驳回的商品');
+    return;
+  }
+  const validRows = selectedRows.value.filter((r) => r.poolAuditId);
+  if (validRows.length === 0) {
+    proxy?.$modal.msgWarning('所选商品均未关联入池单,无法驳回');
+    return;
+  }
+  try {
+    const { value: reason } = await ElMessageBox.prompt(
+      `已选 ${validRows.length} 个商品,请输入驳回原因`,
+      '批量驳回',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        inputPlaceholder: '请输入驳回原因',
+        inputValidator: (val) => (val && val.trim() ? true : '驳回原因不能为空')
+      }
+    );
+    // 按 poolAuditId 分组
+    const groups = new Map<string | number, Array<string | number>>();
+    for (const row of validRows) {
+      const pid = row.poolAuditId!;
+      if (!groups.has(pid)) groups.set(pid, []);
+      groups.get(pid)!.push((row as any).productId ?? row.id);
+    }
+    await Promise.all(
+      Array.from(groups.entries()).map(([poolAuditId, productIds]) =>
+        batchAudit({ poolAuditId, productIds, auditStatus: 3, reason: reason.trim() })
+      )
+    );
+    proxy?.$modal.msgSuccess(`批量驳回成功(共 ${validRows.length} 条)`);
+    await getList();
+  } catch (error: any) {
+    if (error !== 'cancel') {
+      console.error('批量驳回失败:', error);
+    }
+  }
+};
+
 /** 新增按钮操作 */
 const handleAdd = () => {
   router.push('/product/base/add');

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

@@ -107,6 +107,13 @@
     </el-card>
 
     <el-card shadow="never">
+      <!-- 批量操作工具栏 -->
+      <div  class="mb-2 flex items-center gap-2">
+        <span class="text-sm text-gray-500">已选 <span class="text-blue-600 font-bold">{{ selectedRows.length }}</span> 条</span>
+        <el-button type="success" plain size="small" icon="Check" @click="handleBatchAudit">批量审核通过</el-button>
+        <el-button type="danger" plain size="small" icon="Close" @click="handleBatchReject">批量驳回</el-button>
+      </div>
+
       <el-table v-loading="loading" border :data="baseList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="入池名称" align="center" prop="name" width="100" fixed="left" />
@@ -275,7 +282,7 @@ import {
   changeProductType,
   getProductStatusCount
 } from '@/api/product/base';
-import { selectPoolAuditProductPage, audit } from '@/api/product/poolAudit';
+import { selectPoolAuditProductPage, audit, batchAudit } from '@/api/product/poolAudit';
 import { ProductListVO } from '@/api/product/poolAudit/types';
 import { generatePPT } from '@/utils/pptPlugin';
 import { addProductSelf } from '@/api/product/productSelf';
@@ -296,6 +303,7 @@ const buttonLoading = ref(false);
 const loading = ref(true);
 const showSearch = ref(true);
 const ids = ref<Array<string | number>>([]);
+const selectedRows = ref<ProductListVO[]>([]);
 const single = ref(true);
 const multiple = ref(true);
 const total = ref(0);
@@ -518,12 +526,89 @@ const resetQuery = () => {
 };
 
 /** 多选框选中数据 */
-const handleSelectionChange = (selection: BaseVO[]) => {
+const handleSelectionChange = (selection: ProductListVO[]) => {
   ids.value = selection.map((item) => item.id);
+  selectedRows.value = selection;
   single.value = selection.length != 1;
   multiple.value = !selection.length;
 };
 
+/** 批量审核通过 */
+const handleBatchAudit = async () => {
+  if (selectedRows.value.length === 0) {
+    proxy?.$modal.msgWarning('请先选择要审核的商品');
+    return;
+  }
+  // 过滤没有 poolAuditId 的行
+  const validRows = selectedRows.value.filter((r) => r.poolAuditId);
+  if (validRows.length === 0) {
+    proxy?.$modal.msgWarning('所选商品均未关联入池单,无法审核');
+    return;
+  }
+  await proxy?.$modal.confirm(`确认批量审核通过所选 ${validRows.length} 个商品吗?`);
+  try {
+    // 按 poolAuditId 分组
+    const groups = new Map<string | number, Array<string | number>>();
+    for (const row of validRows) {
+      const pid = row.poolAuditId!;
+      if (!groups.has(pid)) groups.set(pid, []);
+      groups.get(pid)!.push((row as any).productId ?? row.id);
+    }
+    await Promise.all(
+      Array.from(groups.entries()).map(([poolAuditId, productIds]) =>
+        batchAudit({ poolAuditId, productIds, auditStatus: 2 })
+      )
+    );
+    proxy?.$modal.msgSuccess(`批量审核通过成功(共 ${validRows.length} 条)`);
+    await getList();
+  } catch (error) {
+    console.error('批量审核失败:', error);
+  }
+};
+
+/** 批量驳回 */
+const handleBatchReject = async () => {
+  if (selectedRows.value.length === 0) {
+    proxy?.$modal.msgWarning('请先选择要驳回的商品');
+    return;
+  }
+  const validRows = selectedRows.value.filter((r) => r.poolAuditId);
+  if (validRows.length === 0) {
+    proxy?.$modal.msgWarning('所选商品均未关联入池单,无法驳回');
+    return;
+  }
+  try {
+    const { value: reason } = await ElMessageBox.prompt(
+      `已选 ${validRows.length} 个商品,请输入驳回原因`,
+      '批量驳回',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        inputPlaceholder: '请输入驳回原因',
+        inputValidator: (val) => (val && val.trim() ? true : '驳回原因不能为空')
+      }
+    );
+    // 按 poolAuditId 分组
+    const groups = new Map<string | number, Array<string | number>>();
+    for (const row of validRows) {
+      const pid = row.poolAuditId!;
+      if (!groups.has(pid)) groups.set(pid, []);
+      groups.get(pid)!.push((row as any).productId ?? row.id);
+    }
+    await Promise.all(
+      Array.from(groups.entries()).map(([poolAuditId, productIds]) =>
+        batchAudit({ poolAuditId, productIds, auditStatus: 3, reason: reason.trim() })
+      )
+    );
+    proxy?.$modal.msgSuccess(`批量驳回成功(共 ${validRows.length} 条)`);
+    await getList();
+  } catch (error: any) {
+    if (error !== 'cancel') {
+      console.error('批量驳回失败:', error);
+    }
+  }
+};
+
 /** 新增按钮操作 */
 const handleAdd = () => {
   router.push('/product/base/add');

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

@@ -107,6 +107,13 @@
     </el-card>
 
     <el-card shadow="never">
+      <!-- 批量操作工具栏 -->
+      <div  class="mb-2 flex items-center gap-2">
+        <span class="text-sm text-gray-500">已选 <span class="text-blue-600 font-bold">{{ selectedRows.length }}</span> 条</span>
+        <el-button type="success" plain size="small" icon="Check" @click="handleBatchAudit">批量审核通过</el-button>
+        <el-button type="danger" plain size="small" icon="Close" @click="handleBatchReject">批量驳回</el-button>
+      </div>
+
       <el-table v-loading="loading" border :data="baseList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="入池名称" align="center" prop="name" width="100" fixed="left"/>
@@ -275,7 +282,7 @@ import {
   changeProductType,
   getProductStatusCount
 } from '@/api/product/base';
-import { selectPoolAuditProductPage, audit } from '@/api/product/poolAudit';
+import { selectPoolAuditProductPage, audit, batchAudit } from '@/api/product/poolAudit';
 import { ProductListVO } from '@/api/product/poolAudit/types';
 import { generatePPT } from '@/utils/pptPlugin';
 import { addProductSelf } from '@/api/product/productSelf';
@@ -296,6 +303,7 @@ const buttonLoading = ref(false);
 const loading = ref(true);
 const showSearch = ref(true);
 const ids = ref<Array<string | number>>([]);
+const selectedRows = ref<ProductListVO[]>([]);
 const single = ref(true);
 const multiple = ref(true);
 const total = ref(0);
@@ -518,8 +526,9 @@ const resetQuery = () => {
 };
 
 /** 多选框选中数据 */
-const handleSelectionChange = (selection: BaseVO[]) => {
+const handleSelectionChange = (selection: ProductListVO[]) => {
   ids.value = selection.map((item) => item.id);
+  selectedRows.value = selection;
   single.value = selection.length != 1;
   multiple.value = !selection.length;
 };
@@ -739,6 +748,82 @@ const handleAddToExquisitePool = async (row: BaseVO) => {
   }
 };
 
+/** 批量审核通过 */
+const handleBatchAudit = async () => {
+  if (selectedRows.value.length === 0) {
+    proxy?.$modal.msgWarning('请先选择要审核的商品');
+    return;
+  }
+  // 过滤没有 poolAuditId 的行
+  const validRows = selectedRows.value.filter((r) => r.poolAuditId);
+  if (validRows.length === 0) {
+    proxy?.$modal.msgWarning('所选商品均未关联入池单,无法审核');
+    return;
+  }
+  await proxy?.$modal.confirm(`确认批量审核通过所选 ${validRows.length} 个商品吗?`);
+  try {
+    // 按 poolAuditId 分组
+    const groups = new Map<string | number, Array<string | number>>();
+    for (const row of validRows) {
+      const pid = row.poolAuditId!;
+      if (!groups.has(pid)) groups.set(pid, []);
+      groups.get(pid)!.push((row as any).productId ?? row.id);
+    }
+    await Promise.all(
+      Array.from(groups.entries()).map(([poolAuditId, productIds]) =>
+        batchAudit({ poolAuditId, productIds, auditStatus: 2 })
+      )
+    );
+    proxy?.$modal.msgSuccess(`批量审核通过成功(共 ${validRows.length} 条)`);
+    await getList();
+  } catch (error) {
+    console.error('批量审核失败:', error);
+  }
+};
+
+/** 批量驳回 */
+const handleBatchReject = async () => {
+  if (selectedRows.value.length === 0) {
+    proxy?.$modal.msgWarning('请先选择要驳回的商品');
+    return;
+  }
+  const validRows = selectedRows.value.filter((r) => r.poolAuditId);
+  if (validRows.length === 0) {
+    proxy?.$modal.msgWarning('所选商品均未关联入池单,无法驳回');
+    return;
+  }
+  try {
+    const { value: reason } = await ElMessageBox.prompt(
+      `已选 ${validRows.length} 个商品,请输入驳回原因`,
+      '批量驳回',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        inputPlaceholder: '请输入驳回原因',
+        inputValidator: (val) => (val && val.trim() ? true : '驳回原因不能为空')
+      }
+    );
+    // 按 poolAuditId 分组
+    const groups = new Map<string | number, Array<string | number>>();
+    for (const row of validRows) {
+      const pid = row.poolAuditId!;
+      if (!groups.has(pid)) groups.set(pid, []);
+      groups.get(pid)!.push((row as any).productId ?? row.id);
+    }
+    await Promise.all(
+      Array.from(groups.entries()).map(([poolAuditId, productIds]) =>
+        batchAudit({ poolAuditId, productIds, auditStatus: 3, reason: reason.trim() })
+      )
+    );
+    proxy?.$modal.msgSuccess(`批量驳回成功(共 ${validRows.length} 条)`);
+    await getList();
+  } catch (error: any) {
+    if (error !== 'cancel') {
+      console.error('批量驳回失败:', error);
+    }
+  }
+};
+
 /** 审核通过 */
 const handleAudit = async (row: ProductListVO) => {
   if (!row.poolAuditId) {

+ 70 - 9
src/views/product/poolAuditReview/index.vue

@@ -9,16 +9,22 @@
         <el-descriptions-item label="编号">{{ auditInfo?.id }}</el-descriptions-item>
         <el-descriptions-item label="产品池类型">{{ poolTypeLabel(auditInfo?.type) }}</el-descriptions-item>
         <el-descriptions-item label="申请时间">{{ auditInfo?.createTime }}</el-descriptions-item>
-        <el-descriptions-item label="审核时间">{{ auditInfo?.auditTime || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="状态">
+        <!-- <el-descriptions-item label="审核时间">{{ auditInfo?.auditTime || '-' }}</el-descriptions-item> -->
+        <!-- <el-descriptions-item label="状态">
           <el-tag :type="getStatusTagType(auditInfo?.productReviewStatus)">{{ getStatusLabel(auditInfo?.productReviewStatus) }}</el-tag>
-        </el-descriptions-item>
+        </el-descriptions-item> -->
         <el-descriptions-item label="创建人">{{ auditInfo?.createByName }}</el-descriptions-item>
-        <el-descriptions-item label="审核人">{{ auditInfo?.auditByName || '-' }}</el-descriptions-item>
+        <!-- <el-descriptions-item label="审核人">{{ auditInfo?.auditByName || '-' }}</el-descriptions-item> -->
         <el-descriptions-item label="备注">{{ auditInfo?.remark || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="驳回意见">{{ auditInfo?.reviewReason || '-' }}</el-descriptions-item>
+        <!-- <el-descriptions-item label="驳回意见">{{ auditInfo?.reviewReason || '-' }}</el-descriptions-item> -->
         <el-descriptions-item label="附件">
-          <el-tag v-if="auditInfo?.attachment" type="success">已上传</el-tag>
+          <template v-if="attachmentFiles.length > 0">
+            <div v-for="file in attachmentFiles" :key="String(file.ossId)" class="mb-1">
+              <el-link type="primary" :underline="true" @click="proxy?.$download.oss(file.ossId)">
+                {{ file.originalName || file.fileName }}
+              </el-link>
+            </div>
+          </template>
           <el-tag v-else type="info">暂无附件</el-tag>
         </el-descriptions-item>
       </el-descriptions>
@@ -32,6 +38,11 @@
       <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" />
+        <el-tab-pane label="审核驳回" name="3" />
+      </el-tabs>
       <el-table ref="poolTableRef" v-loading="listLoading" :data="poolList" border @selection-change="handlePoolSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column type="index" label="序号" width="60" align="center" />
@@ -91,6 +102,20 @@
             <span>{{ getSupplierName(scope.row.supplier) }}</span>
           </template>
         </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>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="审核意见" align="center" prop="auditReason" min-width="120" show-overflow-tooltip>
+          <template #default="scope">
+            <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>
@@ -130,6 +155,8 @@
 <script setup name="PoolAuditReview" lang="ts">
 import { useRouter, useRoute } from 'vue-router';
 import { getPoolAudit, updatePoolAudit, getPoolAuditProductPage, batchAudit } from '@/api/product/poolAudit';
+import { listByIds } from '@/api/system/oss';
+import { OssVO } from '@/api/system/oss/types';
 import { PoolAuditVO } from '@/api/product/poolAudit/types';
 import { delPoolLinkAudit } from '@/api/product/poolLinkAudit';
 import { PoolLinkVO } from '@/api/product/poolLink/types';
@@ -144,7 +171,10 @@ const poolAuditId = computed(() => (route.params.id || route.query.id) as string
 
 // 审核信息
 const auditInfoLoading = ref(false);
-const auditInfo = ref<(PoolAuditVO & { createTime?: string; auditTime?: string }) | undefined>();
+const auditInfo = ref<(PoolAuditVO & { createTime?: string; auditTime?: string; attachment?: string }) | undefined>();
+
+// 附件文件列表
+const attachmentFiles = ref<OssVO[]>([]);
 
 // 入池清单
 const listLoading = ref(false);
@@ -152,8 +182,12 @@ const poolList = ref<PoolLinkVO[]>([]);
 const listTotal = ref(0);
 const listQuery = ref({
   pageNum: 1,
-  pageSize: 10
+  pageSize: 10,
+  productReviewStatus: undefined as string | undefined
 });
+
+// 审核状态 Tab
+const activeTab = ref('');
 const selectedPoolProducts = ref<PoolLinkVO[]>([]);
 const poolTableRef = ref<any>();
 
@@ -215,6 +249,23 @@ const getSupplierName = (supplierId: string | number | undefined): string => {
   return supplier?.enterpriseName || supplier?.shortName || '-';
 };
 
+/** 监听附件变化,加载附件文件列表 */
+watch(
+  () => auditInfo.value?.attachment,
+  async (val) => {
+    if (val) {
+      try {
+        const res = await listByIds(val);
+        attachmentFiles.value = res.data || [];
+      } catch {
+        attachmentFiles.value = [];
+      }
+    } else {
+      attachmentFiles.value = [];
+    }
+  }
+);
+
 /** 加载审核信息 */
 const loadAuditInfo = async () => {
   if (!poolAuditId.value) return;
@@ -243,11 +294,14 @@ const getSupplierList = async () => {
 const getPoolList = async () => {
   listLoading.value = true;
   try {
-    const params = {
+    const params: Record<string, any> & { pageNum: number; pageSize: number } = {
       pageNum: listQuery.value.pageNum,
       pageSize: listQuery.value.pageSize,
       poolAuditId: poolAuditId.value
     };
+    if (listQuery.value.productReviewStatus) {
+      params.productReviewStatus = listQuery.value.productReviewStatus;
+    }
     const res = await getPoolAuditProductPage(params);
     poolList.value = (res.rows || res.data || []) as any;
     listTotal.value = res.total || 0;
@@ -260,6 +314,13 @@ const getPoolList = async () => {
   }
 };
 
+/** Tab 切换 */
+const handleTabChange = (name: string | number) => {
+  listQuery.value.productReviewStatus = name === '' ? undefined : String(name);
+  listQuery.value.pageNum = 1;
+  getPoolList();
+};
+
 /** 入池清单选择变化 */
 const handlePoolSelectionChange = (selection: PoolLinkVO[]) => {
   selectedPoolProducts.value = selection;

+ 23 - 6
src/views/product/poolLinkAudit/index.vue

@@ -53,10 +53,10 @@
         <!-- 第三行:附件 -->
         <el-row>
           <el-col :span="8">
-            <el-form-item label="申请类型" prop="applyType">
+            <el-form-item v-if="auditForm.type !== '0'" label="申请类型" prop="applyType">
               <el-radio-group v-model="auditForm.applyType">
-                <el-radio :value="0">更新</el-radio>
-                <el-radio :value="1">出</el-radio>
+                <el-radio :value="0">入池</el-radio>
+                <el-radio :value="1">出</el-radio>
               </el-radio-group>
             </el-form-item>
           </el-col>
@@ -130,6 +130,19 @@
           </template>
         </el-table-column>
 
+        <el-table-column v-if="auditForm.type === '2'" label="协议价" align="center" width="140">
+          <template #default="scope">
+            <el-input-number
+              v-model="tempAgreementPrices[scope.row.id]"
+              :min="0"
+              :precision="2"
+              :controls="false"
+              size="small"
+              style="width: 120px"
+              placeholder="请输入协议价"
+            />
+          </template>
+        </el-table-column>
         <el-table-column label="采购信息" align="center" width="150">
           <template #default="scope">
             <div class="text-left" style="font-size: 12px">
@@ -191,7 +204,7 @@
         <el-table-column label="操作" align="center" width="120" fixed="right">
           <template #default="scope">
             <div class="flex flex-col gap-1">
-              <el-link type="danger" :underline="false" @click="handleRemoveProduct(scope.row)">移除商品池</el-link>
+              <el-link type="danger" :underline="false" @click="handleRemoveProduct(scope.row)">移除</el-link>
             </div>
           </template>
         </el-table-column>
@@ -1101,8 +1114,12 @@ const getProductList = async () => {
   addProductDialog.loading = true;
   try {
     const params = { ...addProductQuery.value };
-    // 标准产品池(type=1)或协议产品池(type=2)时查询自营产品
-    if (auditForm.type === '1' || auditForm.type === '2') {
+    // 只查询已上架的商品
+    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;

+ 0 - 12
src/views/product/protocolInfo/index.vue

@@ -10,13 +10,6 @@
             <el-form-item label="客户名称" prop="customerName">
               <el-input v-model="queryParams.customerName" placeholder="请输入客户名称" clearable @keyup.enter="handleQuery" />
             </el-form-item>
-            <el-form-item label="状态" prop="protocolStatus">
-              <el-select v-model="queryParams.protocolStatus" placeholder="请选择" clearable>
-                <el-option label="生效" value="1" />
-                <el-option label="待审核" value="0" />
-                <el-option label="失效" value="2" />
-              </el-select>
-            </el-form-item>
             <el-form-item>
               <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
               <el-button type="primary" icon="Refresh" @click="resetQuery">重置</el-button>
@@ -56,11 +49,6 @@
           </template>
         </el-table-column>
         <el-table-column label="商品数量" align="center" prop="productNum" width="100" />
-        <el-table-column label="状态" align="center" prop="approvalStatus" width="80">
-          <template #default="scope">
-            <span>{{ getStatusLabel(scope.row.protocolStatus) }}</span>
-          </template>
-        </el-table-column>
         <el-table-column label="操作" align="center" fixed="right" width="300" class-name="small-padding fixed-width">
           <template #default="scope">
             <el-button link type="primary" @click="handleDetail(scope.row)" >基本信息</el-button>