Sfoglia il codice sorgente

feat(product): 添加商品选择页面并完善表单验证功能

- 在商品基础新增页面添加单位必填验证和移除默认值设置
- 修复促销标题字段绑定错误问题
- 为商品审核页面添加详情查看路由和功能
- 更新税务编码组件以禁用非叶子节点选择并添加视觉反馈
- 优化商品审核列表增加预览和查看详情操作按钮
- 修改商品详情抽屉中申请类型字段映射和客户信息获取逻辑
- 创建全新的商品选择页面,包含完整的搜索、筛选和库存管理功能
肖路 3 settimane fa
parent
commit
e38f196939

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

@@ -385,6 +385,11 @@ export interface BaseForm extends BaseEntity {
    */
   sectionNo?: string;
 
+  /**
+   * 促销标题
+   */
+  promotionTitle?: string;
+
   /**
    * 包装规格
    */

+ 29 - 7
src/components/TaxCodeSelect/index.vue

@@ -45,26 +45,27 @@
             border
             highlight-current-row
             height="360px"
+            :row-class-name="rowClassName"
             @row-dblclick="handleRowDblClick"
           >
             <el-table-column label="编码" align="center" prop="taxationNo" min-width="110">
               <template #default="{ row }">
-                <el-link type="primary" :underline="false">{{ row.taxationNo }}</el-link>
+                <el-link :type="isRowDisabled(row) ? 'info' : 'primary'" :disabled="isRowDisabled(row)" :underline="false">{{ row.taxationNo }}</el-link>
               </template>
             </el-table-column>
             <el-table-column label="合并编码" align="center" prop="mergeNo" min-width="130" show-overflow-tooltip>
               <template #default="{ row }">
-                <el-link type="primary" :underline="false">{{ row.mergeNo }}</el-link>
+                <el-link :type="isRowDisabled(row) ? 'info' : 'primary'" :disabled="isRowDisabled(row)" :underline="false">{{ row.mergeNo }}</el-link>
               </template>
             </el-table-column>
             <el-table-column label="名称" align="center" prop="name" min-width="120" show-overflow-tooltip>
               <template #default="{ row }">
-                <el-link type="primary" :underline="false">{{ row.name }}</el-link>
+                <el-link :type="isRowDisabled(row) ? 'info' : 'primary'" :disabled="isRowDisabled(row)" :underline="false">{{ row.name }}</el-link>
               </template>
             </el-table-column>
             <el-table-column label="简称" align="center" prop="abbreviation" min-width="100" show-overflow-tooltip>
               <template #default="{ row }">
-                <el-link type="primary" :underline="false">{{ row.abbreviation }}</el-link>
+                <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 />
@@ -171,7 +172,6 @@ const getList = async () => {
   loading.value = true;
   try {
     const res = await listTaxCode(queryParams.value);
-    // 后端已按 isBottom='0' 过滤,仅返回无下级的叶子节点
     listData.value = res.rows ?? [];
     total.value = res.total ?? 0;
   } finally {
@@ -203,9 +203,22 @@ const handleSearch = () => {
   getList();
 };
 
+/** 判断行是否禁用:非叶子节点(isBottom === 0)不可选 */
+const isRowDisabled = (row: TaxCodeVO): boolean => {
+  return Number((row as any)?.isBottom) === 0;
+};
+
+/** 行样式类名:禁用行显示为灰色 */
+const rowClassName = ({ row }: { row: TaxCodeVO }): string => {
+  return isRowDisabled(row) ? 'tax-code-row-disabled' : '';
+};
+
 /** 双击行选择 */
 const handleRowDblClick = (row: TaxCodeVO) => {
-  if (row.isBottom !== '0') return;
+  if (isRowDisabled(row)) {
+    proxy?.$modal.msgWarning('该节点不是叶子节点,不可选择');
+    return;
+  }
   emit('select', row);
   dialog.closeDialog();
 };
@@ -220,7 +233,6 @@ const open = (id?: string | number) => {
     parentId: undefined,
     name: undefined,
     taxationNo: undefined,
-    isBottom: '0',
     params: {}
   };
   dialog.openDialog();
@@ -274,4 +286,14 @@ defineExpose({ open, close: dialog.closeDialog });
   display: flex;
   align-items: center;
 }
+
+:deep(.tax-code-row-disabled) {
+  background-color: var(--el-fill-color-light) !important;
+  color: var(--el-text-color-disabled);
+  cursor: not-allowed;
+}
+
+:deep(.tax-code-row-disabled:hover > td.el-table__cell) {
+  background-color: var(--el-fill-color-light) !important;
+}
 </style>

+ 6 - 0
src/router/index.ts

@@ -137,6 +137,12 @@ export const constantRoutes: RouteRecordRaw[] = [
         name: 'BaseAuditEdit',
         meta: { title: '商品审核', activeMenu: '/product/baseAudit' }
       },
+      {
+        path: 'baseAudit/view/:id',
+        component: () => import('@/views/product/baseAudit/view.vue'),
+        name: 'BaseAuditView',
+        meta: { title: '查看商品', activeMenu: '/product/baseAudit' }
+      },
       {
         path: 'base/detail/:id',
         component: () => import('@/views/product/base/add.vue'),

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

@@ -235,13 +235,14 @@
               </el-col>
 
               <el-col :span="12">
-                <el-form-item label="单位:">
+                <el-form-item label="单位:" required>
                   <el-select
                     v-model="productForm.unitId"
                     placeholder="请选择"
                     clearable
                     class="w-full"
                     :disabled="productForm.productReviewStatus === 1"
+
                   >
                     <el-option v-for="option in unitOptions" :key="option.id" :label="`${option.unitNo},${option.unitName}`" :value="option.id" />
                   </el-select>
@@ -308,7 +309,7 @@
 
             <!-- 促销标题 -->
             <el-form-item label="促销标题:">
-              <el-input v-model="productForm.packagingSpec" type="textarea" :rows="3" placeholder="请输入促销标题" maxlength="300" show-word-limit />
+              <el-input v-model="productForm.promotionTitle" type="textarea" :rows="3" placeholder="请输入促销标题" maxlength="300" show-word-limit />
             </el-form-item>
 
             <!-- 商品简介 -->
@@ -1516,10 +1517,6 @@ const getUnitOptions = async () => {
   try {
     const res = await getUnitList();
     unitOptions.value = res.data || [];
-    // 如果是新增模式且有选项,设置第一个为默认值
-    if (!route.params.id && unitOptions.value.length > 0 && !productForm.unitId) {
-      productForm.unitId = unitOptions.value[0].id;
-    }
   } catch (error) {
     console.error('获取单位列表失败:', error);
   }

+ 810 - 0
src/views/product/base/selected.vue

@@ -0,0 +1,810 @@
+<template>
+  <div class="p-2">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" label-width="90px">
+            <el-row :gutter="20">
+              <el-col :span="6">
+                <el-form-item label="商品编号" prop="productNo">
+                  <el-input v-model="queryParams.productNo" placeholder="请输入商品编号" clearable @keyup.enter="handleQuery" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="商品名称" prop="itemName">
+                  <el-input v-model="queryParams.itemName" placeholder="请输入商品名称" clearable @keyup.enter="handleQuery" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="商品品牌" prop="brandId">
+                  <el-select
+                    v-model="queryParams.brandId"
+                    placeholder="请输入品牌名称搜索"
+                    filterable
+                    remote
+                    clearable
+                    :remote-method="handleBrandSearch"
+                    :loading="brandLoading"
+                    style="width: 100%"
+                    @keyup.enter="handleQuery"
+                  >
+                    <el-option v-for="item in brandOptions" :key="item.id" :label="item.brandName" :value="item.id" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="上下架状态" prop="productStatus">
+                  <el-select v-model="queryParams.productStatus" placeholder="请选择" clearable>
+                    <el-option label="已上架" :value="1" />
+                    <el-option label="下架" :value="0" />
+                    <el-option label="上架中" :value="2" />
+                    <el-option label="驳回上架" :value="3" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-row :gutter="20">
+              <el-col :span="6">
+                <el-form-item label="商品类型" prop="bottomCategoryId">
+                  <el-tree-select
+                    v-model="queryParams.bottomCategoryId"
+                    :data="categoryOptions"
+                    :props="{ value: 'id', label: 'label', children: 'children' } as any"
+                    value-key="id"
+                    placeholder="请选择商品类型"
+                    clearable
+                    check-strictly
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="18">
+                <el-form-item label="商品分类">
+                  <category-cascade-select
+                    ref="categoryCascadeRef"
+                    v-model:top-category-id="queryParams.topCategoryId"
+                    v-model:medium-category-id="queryParams.mediumCategoryId"
+                    v-model:bottom-category-id="queryParams.bottomCategoryId"
+                  />
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-row>
+              <el-col :span="24" class="text-left">
+                <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+              </el-col>
+            </el-row>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <!-- 统计信息区域 -->
+    <el-card shadow="never" class="mb-[10px]">
+      <div class="flex items-center text-sm text-gray-600">
+        <span>商品总数: </span>
+        <span class="text-blue-600 mx-1"
+        >总=<span class="text-red-600">{{ statistics.total || 0 }}</span
+        >条</span
+        >
+        <span class="mx-2">【上架/总数({{ statistics.onSale || 0 }}/{{ statistics.total || 0 }})】</span>
+        <span class="mx-2"
+        >审核状态: 待审核<span class="text-red-600">{{ statistics.waitAudit || 0 }}</span
+        >条,通过<span class="text-green-600">{{ statistics.auditPass || 0 }}</span
+        >条,驳回<span class="text-orange-600">{{ statistics.auditReject || 0 }}</span
+        >条</span
+        >
+        <span class="mx-2"
+        >上下架状态: 已上架<span class="text-green-600">{{ statistics.onSale || 0 }}</span
+        >条,下架<span class="text-gray-600">{{ statistics.offSale || 0 }}</span
+        >条</span
+        >
+        <div class="ml-auto flex gap-2">
+          <el-button type="primary" icon="Plus" @click="handleAdd">商品新增</el-button>
+          <!-- <el-button type="warning" icon="Check" @click="handleGoReview">商品审核</el-button> -->
+          <!--          <el-button plain>批量操作</el-button>-->
+          <el-button plain icon="Download" @click="handleExport">导出</el-button>
+          <el-button circle icon="Refresh" @click="getList"></el-button>
+        </div>
+      </div>
+    </el-card>
+
+    <el-card shadow="never">
+      <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="productNo" width="120" fixed="left">
+          <template #default="scope">
+            <el-link type="primary" @click="handleView(scope.row)">{{ scope.row.productNo }}</el-link>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品图片" align="center" prop="productImage" width="100">
+          <template #default="scope">
+            <image-preview :src="scope.row.productImage" :width="60" :height="60" />
+          </template>
+        </el-table-column>
+        <el-table-column label="商品信息" align="center" min-width="250">
+          <template #default="scope">
+            <div class="text-left">
+              <div style="white-space: normal; word-break: break-all; line-height: 1.4">{{ scope.row.itemName }}</div>
+              <div class="text-gray-500" style="font-size: 12px">品牌: {{ scope.row.brandName || '-' }}</div>
+              <div class="text-gray-500" style="font-size: 12px">分类: {{ scope.row.topCategoryName+'-'+scope.row.mediumCategoryName+'-'+ scope.row.bottomCategoryName}}</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="单位" align="center" width="100">
+          <template #default="scope">
+            <div class="text-left">
+              <div class="text-gray-500" style="font-size: 12px">单位: {{ scope.row.unitName || '-' }}</div>
+              <div class="text-gray-500" style="font-size: 12px">起订量: {{ scope.row.minOrderQuantity || '-' }}</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="价格信息" align="center" width="120">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px">
+              <div>
+                <span class="text-gray-500">市场价:</span>
+                <span class="text-red-500">¥{{ scope.row.marketPrice || '0.00' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">官网价:</span>
+                <span class="text-red-500">¥{{ scope.row.memberPrice || '0.00' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">最低价:</span>
+                <span class="text-red-500">¥{{ scope.row.minSellingPrice || '0.00' }}</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="采购信息" align="center" width="150">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px">
+              <div>
+                <span class="text-gray-500">采购价:</span>
+                <span>¥{{ scope.row.purchasingPrice || '0.00' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">暂估毛利率:</span>
+                <span>{{ scope.row.memberPrice ? (((scope.row.memberPrice - (scope.row.purchasingPrice || 0)) / scope.row.memberPrice) * 100).toFixed(2) : '0.00' }}%</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="库存情况" align="center" width="140">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px">
+              <div>
+                <span class="text-gray-500">总库存:</span>
+                <span>{{ scope.row.totalInventory ?? '-' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">可用库存:</span>
+                <span>{{ scope.row.nowInventory ?? '-' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">虚拟库存:</span>
+                <span>{{ scope.row.virtualInventory ?? '-' }}</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <!--        <el-table-column label="数据来源" align="center" prop="dataSource" width="80">-->
+        <!--          <template #default="scope">-->
+        <!--            <span>{{ scope.row.dataSource || '-' }}</span>-->
+        <!--          </template>-->
+        <!--        </el-table-column>-->
+        <el-table-column label="是否自营" align="center" width="80">
+          <template #default="scope">
+            <span v-if="scope.row.isSelf === 1">是</span>
+            <span v-else-if="scope.row.isSelf === 0">否</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>
+            <el-tag v-else-if="scope.row.productStatus === 0" type="warning">下架</el-tag>
+            <el-tag v-else-if="scope.row.productStatus === 2" type="info">上架中</el-tag>
+            <el-tag v-else type="info">未知</el-tag>
+          </template>
+        </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>
+
+            <!-- 其他状态(待提交、审核驳回等):显示编辑 -->
+            <div v-else 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>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 游标分页控制 -->
+      <pagination
+        v-show="baseList.length > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        v-model:way="queryParams.way"
+        :cursor-mode="true"
+        :has-more="hasMore"
+        @pagination="getList"
+      />
+    </el-card>
+    <!-- 库存修改弹框 -->
+    <el-dialog v-model="inventoryDialog.visible" title="修改库存" width="500px" :close-on-click-modal="false">
+      <div v-loading="inventoryDialog.loading">
+        <el-form ref="inventoryFormRef" :model="inventoryForm" :rules="inventoryRules" label-width="110px">
+          <el-form-item label="虚拟库存" prop="virtualInventory">
+            <el-input-number
+              v-model="inventoryForm.virtualInventory"
+              :min="0"
+              :precision="0"
+              controls-position="right"
+              style="width: 100%"
+              placeholder="请输入虚拟库存"
+            />
+          </el-form-item>
+        </el-form>
+      </div>
+      <template #footer>
+        <el-button @click="inventoryDialog.visible = false">取消</el-button>
+        <el-button type="primary" :loading="inventoryDialog.submitLoading" @click="submitInventory">确定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="Base" lang="ts">
+import {
+  listBase,
+  getBase,
+  delBase,
+  brandList,
+  updateBase,
+  categoryTree,
+  shelfReview,
+  changeProductType,
+  getProductStatusCount
+} from '@/api/product/base';
+import { generatePPT } from '@/utils/pptPlugin';
+import { addProductSelf } from '@/api/product/productSelf';
+import { addProductExquisite } from '@/api/product/productExquisite';
+import { PriceInventoryForm } from '@/api/product/priceInventory/types';
+import { BaseVO, BaseQuery, BaseForm, StatusCountVo } from '@/api/product/base/types';
+import { BrandVO } from '@/api/product/brand/types';
+import { listBrand } from '@/api/product/brand';
+import { categoryTreeVO } from '@/api/product/category/types';
+import { useRoute, useRouter } from 'vue-router';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const router = useRouter();
+const route = useRoute();
+
+const baseList = ref<BaseVO[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const categoryOptions = ref<categoryTreeVO[]>([]);
+const brandOptions = ref<BrandVO[]>([]);
+const brandLoading = ref(false);
+let brandSearchTimer: ReturnType<typeof setTimeout> | null = null;
+const hasMore = ref(true); // 是否还有更多数据
+// 页面历史记录,存储每页的第一个id和最后一个id,用于支持双向翻页
+const pageHistory = ref([]);
+
+// 分类相关
+const categoryCascadeRef = ref();
+
+// 统计信息
+const statistics = ref<StatusCountVo>({
+  total: 0,
+  onSale: 0,
+  offSale: 0,
+  waitAudit: 0,
+  auditPass: 0,
+  auditReject: 0
+});
+
+const queryFormRef = ref<ElFormInstance>();
+const baseFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: BaseForm = {
+  id: undefined,
+  productNo: undefined,
+  itemName: undefined,
+  brandId: undefined,
+  topCategoryId: undefined,
+  mediumCategoryId: undefined,
+  bottomCategoryId: undefined,
+  unitId: undefined,
+  productImage: undefined,
+  isSelf: undefined,
+  productReviewStatus: undefined,
+  homeRecommended: undefined,
+  categoryRecommendation: undefined,
+  cartRecommendation: undefined,
+  recommendedProductOrder: undefined,
+  isPopular: undefined,
+  isNew: undefined,
+  productStatus: undefined,
+  remark: undefined
+};
+const data = reactive<PageData<BaseForm, BaseQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    productNo: undefined,
+    itemName: undefined,
+    brandId: undefined,
+    productTag: undefined,
+    purchaseNature: undefined,
+    supplierType: undefined,
+    supplierNature: undefined,
+    projectOrg: undefined,
+    topCategoryId: undefined,
+    mediumCategoryId: undefined,
+    bottomCategoryId: undefined,
+    isSelf: 1,
+    productCategory: 2,
+    productReviewStatus: undefined,
+    productStatus: undefined,
+    lastSeenId: undefined, // 游标分页的lastSeenId
+    way: undefined,
+    params: {}
+  },
+  rules: {
+    productNo: [{ required: true, message: '产品编号不能为空', trigger: 'blur' }],
+    itemName: [{ required: true, message: '项目名称不能为空', trigger: 'blur' }],
+    brandId: [{ required: true, message: '品牌id不能为空', trigger: 'blur' }],
+    topCategoryId: [{ required: true, message: '顶级分类id不能为空', trigger: 'blur' }],
+    mediumCategoryId: [{ required: true, message: '中级分类id不能为空', trigger: 'blur' }],
+    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' }],
+    recommendedProductOrder: [{ required: true, message: '推荐产品顺序不能为空', trigger: 'blur' }],
+    isPopular: [{ required: true, message: '是否热门:1=是,0=否不能为空', trigger: 'blur' }],
+    isNew: [{ required: true, message: '是否新品:1=是,0=否不能为空', trigger: 'blur' }],
+    remark: [{ required: true, message: '备注不能为空', trigger: 'blur' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询产品基础信息列表 */
+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 listBase(params);
+    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);
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 初始化路由参数 */
+const initRouteParams = () => {
+  // 从路由参数中获取筛选条件
+  if (route.query.productReviewStatus) {
+    queryParams.value.productReviewStatus = Number(route.query.productReviewStatus);
+  }
+  if (route.query.brandName) {
+    queryParams.value.brandName = route.query.brandName as string;
+  }
+  if (route.query.bottomCategoryId) {
+    queryParams.value.bottomCategoryId = route.query.bottomCategoryId as string;
+  }
+  if (route.query.isSelf) {
+    queryParams.value.isSelf = Number(route.query.isSelf);
+  }
+  if (route.query.productCategory) {
+    queryParams.value.productCategory = Number(route.query.productCategory);
+  }
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  baseFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+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 = []; // 重置页面历史
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  // 手动重置分类字段
+  queryParams.value.topCategoryId = undefined;
+  queryParams.value.mediumCategoryId = undefined;
+  queryParams.value.bottomCategoryId = undefined;
+  categoryCascadeRef.value?.reset();
+  queryParams.value.lastSeenId = undefined;
+  pageHistory.value = []; // 重置页面历史
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: BaseVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  router.push('/product/base/add');
+};
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: BaseVO) => {
+  const _id = row?.id || ids.value[0];
+  router.push(`/product/base/edit/${_id}`);
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: BaseVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('是否确认删除产品基础信息编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
+  await delBase(_ids);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  // 检查是否有选中的商品
+  if (ids.value.length === 0) {
+    proxy?.$modal.msgWarning('请先选择要导出的商品');
+    return;
+  }
+
+  // 获取选中商品的完整信息
+  const selectedProducts = baseList.value.filter(item => ids.value.includes(item.id));
+
+  if (selectedProducts.length === 0) {
+    proxy?.$modal.msgWarning('未找到选中的商品信息');
+    return;
+  }
+
+  // 转换为 generatePPT 需要的格式
+  const products = selectedProducts.map(item => ({
+    image: item.productImage || item.productImageUrl || '',
+    name: item.itemName || '',
+    code: item.productNo || '',
+    spec: item.specification || item.packagingSpec || '-',
+    price: item.minSellingPrice || item.memberPrice || 0
+  }));
+
+  // 默认模板配置
+  const template = {
+    name: '商品展示方案',
+    title: '商品展示方案',
+    themeColor: '#C00000',
+    itemsPerPage: 1, // 每页1个商品,展示更详细
+    cover: '',
+    logo: ''
+  };
+
+  try {
+    proxy?.$modal.loading('正在生成PPT...');
+    await generatePPT(template, products);
+    proxy?.$modal.closeLoading();
+    proxy?.$modal.msgSuccess('PPT导出成功');
+  } catch (error) {
+    proxy?.$modal.closeLoading();
+    console.error('PPT导出失败:', error);
+    proxy?.$modal.msgError('PPT导出失败');
+  }
+};
+
+/** 查看商品详情 */
+const handleView = (row: BaseVO) => {
+  const url = `https://item.xiaoluwebsite.xyz/item?id=${row.id}`;
+  window.open(url, '_blank');
+};
+
+/** 上下架操作 */
+const handleShelf = async (row: BaseVO) => {
+  const isOnShelf = row.productStatus === 1;
+  const action = isOnShelf ? '下架' : '上架';
+  await proxy?.$modal.confirm(`确认${action}该商品吗?`);
+
+  try {
+    // 上架:状态改为2(上架中),下架:状态改为0(下架)
+    const productStatus = isOnShelf ? 0 : 2;
+    await shelfReview({
+      id: row.id,
+      productStatus: productStatus,
+      shelfComments: `${action}操作`
+    });
+    proxy?.$modal.msgSuccess(`${action}成功`);
+    await getList();
+  } catch (error) {
+    console.error(`${action}失败:`, error);
+    proxy?.$modal.msgError(`${action}失败`);
+  }
+};
+
+
+/** 库存修改弹框 */
+const inventoryDialog = reactive({
+  visible: false,
+  loading: false,
+  submitLoading: false
+});
+
+const inventoryFormRef = ref<ElFormInstance>();
+
+const inventoryForm = reactive<PriceInventoryForm>({
+  productId: undefined,
+  totalInventory: undefined,
+  nowInventory: undefined,
+  virtualInventory: undefined
+});
+
+const inventoryRules = {
+  totalInventory: [{ required: true, message: '总库存不能为空', trigger: 'blur' }],
+  nowInventory: [{ required: true, message: '当前可用库存不能为空', trigger: 'blur' }],
+  virtualInventory: [{ required: true, message: '虚拟库存不能为空', trigger: 'blur' }]
+};
+
+/** 打开库存修改弹框 */
+const handleSupply = async (row: BaseVO) => {
+  inventoryForm.id = row.id;
+  inventoryForm.totalInventory = undefined;
+  inventoryForm.nowInventory = undefined;
+  inventoryForm.virtualInventory = undefined;
+  inventoryDialog.loading = true;
+  inventoryDialog.visible = true;
+  try {
+    const res = await getBase(row.id);
+    if (res.data) {
+      inventoryForm.totalInventory = res.data.totalInventory;
+      inventoryForm.nowInventory = res.data.nowInventory;
+      inventoryForm.virtualInventory = res.data.virtualInventory;
+    }
+  } catch (error) {
+    console.error('获取库存信息失败:', error);
+  } finally {
+    inventoryDialog.loading = false;
+  }
+};
+
+/** 提交库存修改 */
+const submitInventory = async () => {
+  await inventoryFormRef.value?.validate();
+  inventoryDialog.submitLoading = true;
+  try {
+    await updateBase({ ...inventoryForm });
+    proxy?.$modal.msgSuccess('库存修改成功');
+    inventoryDialog.visible = false;
+    await getList();
+  } catch (error) {
+    console.error('库存修改失败:', error);
+    proxy?.$modal.msgError('库存修改失败');
+  } finally {
+    inventoryDialog.submitLoading = false;
+  }
+};
+
+/** 停售操作 */
+const handleDiscontinue = async (row: BaseVO) => {
+  await proxy?.$modal.confirm('确认停售该商品吗?停售后商品将无法正常售卖。');
+
+  try {
+    // 调用停售API,将商品类型改为3(停售商品)
+    await changeProductType({
+      id: row.id,
+      productCategory: 3
+    });
+    proxy?.$modal.msgSuccess('停售成功');
+    await getList();
+  } catch (error) {
+    console.error('停售失败:', error);
+    proxy?.$modal.msgError('停售失败');
+  }
+};
+
+/** 加入自营池操作 */
+const handleAddToSelfPool = async (row: BaseVO) => {
+  await proxy?.$modal.confirm('确认将该商品加入自营池吗?');
+
+  try {
+    await addProductSelf({ productId: row.id, auditStatus: 1 });
+    proxy?.$modal.msgSuccess('加入自营池成功');
+    await getList();
+  } catch (error) {
+    console.error('加入自营池失败:', error);
+    proxy?.$modal.msgError('加入自营池失败');
+  }
+};
+
+/** 加入精品池操作 */
+const handleAddToExquisitePool = async (row: BaseVO) => {
+  await proxy?.$modal.confirm('确认将该商品加入精品池吗?');
+
+  try {
+    await addProductExquisite({ productId: row.id, auditStatus: 1 });
+    proxy?.$modal.msgSuccess('加入精品池成功');
+    await getList();
+  } catch (error) {
+    console.error('加入精品池失败:', error);
+    proxy?.$modal.msgError('加入精品池失败');
+  }
+};
+
+/** 跳转到商品审核页面 */
+const handleGoReview = () => {
+  router.push({
+    path: '/product/base/review',
+    query: {
+      productReviewStatus: 1 // 默认显示待审核的商品
+    }
+  });
+};
+
+/** 查询分类树 */
+const getCategoryTree = async () => {
+  const res = await categoryTree();
+  categoryOptions.value = res.data || [];
+};
+
+/** 加载品牌选项(默认100条) */
+const loadBrandOptions = async (keyword?: string) => {
+  brandLoading.value = true;
+  try {
+    const res = await listBrand({ pageNum: 1, pageSize: 100, brandName: keyword });
+    brandOptions.value = res.rows || [];
+  } catch (error) {
+    console.error('加载品牌列表失败:', error);
+  } finally {
+    brandLoading.value = false;
+  }
+};
+
+/** 品牌远程搜索(防抖) */
+const handleBrandSearch = (query: string) => {
+  if (brandSearchTimer) clearTimeout(brandSearchTimer);
+  brandSearchTimer = setTimeout(() => {
+    loadBrandOptions(query || undefined);
+  }, 300);
+};
+
+/** 获取统计信息 */
+const getStatistics = async () => {
+  try {
+    const res = await getProductStatusCount();
+    if (res.data) {
+      statistics.value = res.data;
+    }
+  } catch (error) {
+    console.error('获取统计信息失败:', error);
+  }
+};
+
+
+onMounted(() => {
+  getList();
+  getStatistics();
+  loadBrandOptions();
+});
+</script>

+ 809 - 0
src/views/product/base/self.vue

@@ -0,0 +1,809 @@
+<template>
+  <div class="p-2">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" label-width="90px">
+            <el-row :gutter="20">
+              <el-col :span="6">
+                <el-form-item label="商品编号" prop="productNo">
+                  <el-input v-model="queryParams.productNo" placeholder="请输入商品编号" clearable @keyup.enter="handleQuery" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="商品名称" prop="itemName">
+                  <el-input v-model="queryParams.itemName" placeholder="请输入商品名称" clearable @keyup.enter="handleQuery" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="商品品牌" prop="brandId">
+                  <el-select
+                    v-model="queryParams.brandId"
+                    placeholder="请输入品牌名称搜索"
+                    filterable
+                    remote
+                    clearable
+                    :remote-method="handleBrandSearch"
+                    :loading="brandLoading"
+                    style="width: 100%"
+                    @keyup.enter="handleQuery"
+                  >
+                    <el-option v-for="item in brandOptions" :key="item.id" :label="item.brandName" :value="item.id" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="上下架状态" prop="productStatus">
+                  <el-select v-model="queryParams.productStatus" placeholder="请选择" clearable>
+                    <el-option label="已上架" :value="1" />
+                    <el-option label="下架" :value="0" />
+                    <el-option label="上架中" :value="2" />
+                    <el-option label="驳回上架" :value="3" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-row :gutter="20">
+              <el-col :span="6">
+                <el-form-item label="商品类型" prop="bottomCategoryId">
+                  <el-tree-select
+                    v-model="queryParams.bottomCategoryId"
+                    :data="categoryOptions"
+                    :props="{ value: 'id', label: 'label', children: 'children' } as any"
+                    value-key="id"
+                    placeholder="请选择商品类型"
+                    clearable
+                    check-strictly
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="18">
+                <el-form-item label="商品分类">
+                  <category-cascade-select
+                    ref="categoryCascadeRef"
+                    v-model:top-category-id="queryParams.topCategoryId"
+                    v-model:medium-category-id="queryParams.mediumCategoryId"
+                    v-model:bottom-category-id="queryParams.bottomCategoryId"
+                  />
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-row>
+              <el-col :span="24" class="text-left">
+                <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+              </el-col>
+            </el-row>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <!-- 统计信息区域 -->
+    <el-card shadow="never" class="mb-[10px]">
+      <div class="flex items-center text-sm text-gray-600">
+        <span>商品总数: </span>
+        <span class="text-blue-600 mx-1"
+        >总=<span class="text-red-600">{{ statistics.total || 0 }}</span
+        >条</span
+        >
+        <span class="mx-2">【上架/总数({{ statistics.onSale || 0 }}/{{ statistics.total || 0 }})】</span>
+        <span class="mx-2"
+        >审核状态: 待审核<span class="text-red-600">{{ statistics.waitAudit || 0 }}</span
+        >条,通过<span class="text-green-600">{{ statistics.auditPass || 0 }}</span
+        >条,驳回<span class="text-orange-600">{{ statistics.auditReject || 0 }}</span
+        >条</span
+        >
+        <span class="mx-2"
+        >上下架状态: 已上架<span class="text-green-600">{{ statistics.onSale || 0 }}</span
+        >条,下架<span class="text-gray-600">{{ statistics.offSale || 0 }}</span
+        >条</span
+        >
+        <div class="ml-auto flex gap-2">
+          <el-button type="primary" icon="Plus" @click="handleAdd">商品新增</el-button>
+          <!-- <el-button type="warning" icon="Check" @click="handleGoReview">商品审核</el-button> -->
+          <!--          <el-button plain>批量操作</el-button>-->
+          <el-button plain icon="Download" @click="handleExport">导出</el-button>
+          <el-button circle icon="Refresh" @click="getList"></el-button>
+        </div>
+      </div>
+    </el-card>
+
+    <el-card shadow="never">
+      <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="productNo" width="120" fixed="left">
+          <template #default="scope">
+            <el-link type="primary" @click="handleView(scope.row)">{{ scope.row.productNo }}</el-link>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品图片" align="center" prop="productImage" width="100">
+          <template #default="scope">
+            <image-preview :src="scope.row.productImage" :width="60" :height="60" />
+          </template>
+        </el-table-column>
+        <el-table-column label="商品信息" align="center" min-width="250">
+          <template #default="scope">
+            <div class="text-left">
+              <div style="white-space: normal; word-break: break-all; line-height: 1.4">{{ scope.row.itemName }}</div>
+              <div class="text-gray-500" style="font-size: 12px">品牌: {{ scope.row.brandName || '-' }}</div>
+              <div class="text-gray-500" style="font-size: 12px">分类: {{ scope.row.topCategoryName+'-'+scope.row.mediumCategoryName+'-'+ scope.row.bottomCategoryName}}</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="单位" align="center" width="100">
+          <template #default="scope">
+            <div class="text-left">
+              <div class="text-gray-500" style="font-size: 12px">单位: {{ scope.row.unitName || '-' }}</div>
+              <div class="text-gray-500" style="font-size: 12px">起订量: {{ scope.row.minOrderQuantity || '-' }}</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="价格信息" align="center" width="120">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px">
+              <div>
+                <span class="text-gray-500">市场价:</span>
+                <span class="text-red-500">¥{{ scope.row.marketPrice || '0.00' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">官网价:</span>
+                <span class="text-red-500">¥{{ scope.row.memberPrice || '0.00' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">最低价:</span>
+                <span class="text-red-500">¥{{ scope.row.minSellingPrice || '0.00' }}</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="采购信息" align="center" width="150">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px">
+              <div>
+                <span class="text-gray-500">采购价:</span>
+                <span>¥{{ scope.row.purchasingPrice || '0.00' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">暂估毛利率:</span>
+                <span>{{ scope.row.memberPrice ? (((scope.row.memberPrice - (scope.row.purchasingPrice || 0)) / scope.row.memberPrice) * 100).toFixed(2) : '0.00' }}%</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="库存情况" align="center" width="140">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px">
+              <div>
+                <span class="text-gray-500">总库存:</span>
+                <span>{{ scope.row.totalInventory ?? '-' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">可用库存:</span>
+                <span>{{ scope.row.nowInventory ?? '-' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">虚拟库存:</span>
+                <span>{{ scope.row.virtualInventory ?? '-' }}</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <!--        <el-table-column label="数据来源" align="center" prop="dataSource" width="80">-->
+        <!--          <template #default="scope">-->
+        <!--            <span>{{ scope.row.dataSource || '-' }}</span>-->
+        <!--          </template>-->
+        <!--        </el-table-column>-->
+        <el-table-column label="是否自营" align="center" width="80">
+          <template #default="scope">
+            <span v-if="scope.row.isSelf === 1">是</span>
+            <span v-else-if="scope.row.isSelf === 0">否</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>
+            <el-tag v-else-if="scope.row.productStatus === 0" type="warning">下架</el-tag>
+            <el-tag v-else-if="scope.row.productStatus === 2" type="info">上架中</el-tag>
+            <el-tag v-else type="info">未知</el-tag>
+          </template>
+        </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>
+
+            <!-- 其他状态(待提交、审核驳回等):显示编辑 -->
+            <div v-else 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>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 游标分页控制 -->
+      <pagination
+        v-show="baseList.length > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        v-model:way="queryParams.way"
+        :cursor-mode="true"
+        :has-more="hasMore"
+        @pagination="getList"
+      />
+    </el-card>
+    <!-- 库存修改弹框 -->
+    <el-dialog v-model="inventoryDialog.visible" title="修改库存" width="500px" :close-on-click-modal="false">
+      <div v-loading="inventoryDialog.loading">
+        <el-form ref="inventoryFormRef" :model="inventoryForm" :rules="inventoryRules" label-width="110px">
+          <el-form-item label="虚拟库存" prop="virtualInventory">
+            <el-input-number
+              v-model="inventoryForm.virtualInventory"
+              :min="0"
+              :precision="0"
+              controls-position="right"
+              style="width: 100%"
+              placeholder="请输入虚拟库存"
+            />
+          </el-form-item>
+        </el-form>
+      </div>
+      <template #footer>
+        <el-button @click="inventoryDialog.visible = false">取消</el-button>
+        <el-button type="primary" :loading="inventoryDialog.submitLoading" @click="submitInventory">确定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="Base" lang="ts">
+import {
+  listBase,
+  getBase,
+  delBase,
+  brandList,
+  updateBase,
+  categoryTree,
+  shelfReview,
+  changeProductType,
+  getProductStatusCount
+} from '@/api/product/base';
+import { generatePPT } from '@/utils/pptPlugin';
+import { addProductSelf } from '@/api/product/productSelf';
+import { addProductExquisite } from '@/api/product/productExquisite';
+import { PriceInventoryForm } from '@/api/product/priceInventory/types';
+import { BaseVO, BaseQuery, BaseForm, StatusCountVo } from '@/api/product/base/types';
+import { BrandVO } from '@/api/product/brand/types';
+import { listBrand } from '@/api/product/brand';
+import { categoryTreeVO } from '@/api/product/category/types';
+import { useRoute, useRouter } from 'vue-router';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const router = useRouter();
+const route = useRoute();
+
+const baseList = ref<BaseVO[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const categoryOptions = ref<categoryTreeVO[]>([]);
+const brandOptions = ref<BrandVO[]>([]);
+const brandLoading = ref(false);
+let brandSearchTimer: ReturnType<typeof setTimeout> | null = null;
+const hasMore = ref(true); // 是否还有更多数据
+// 页面历史记录,存储每页的第一个id和最后一个id,用于支持双向翻页
+const pageHistory = ref([]);
+
+// 分类相关
+const categoryCascadeRef = ref();
+
+// 统计信息
+const statistics = ref<StatusCountVo>({
+  total: 0,
+  onSale: 0,
+  offSale: 0,
+  waitAudit: 0,
+  auditPass: 0,
+  auditReject: 0
+});
+
+const queryFormRef = ref<ElFormInstance>();
+const baseFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: BaseForm = {
+  id: undefined,
+  productNo: undefined,
+  itemName: undefined,
+  brandId: undefined,
+  topCategoryId: undefined,
+  mediumCategoryId: undefined,
+  bottomCategoryId: undefined,
+  unitId: undefined,
+  productImage: undefined,
+  isSelf: undefined,
+  productReviewStatus: undefined,
+  homeRecommended: undefined,
+  categoryRecommendation: undefined,
+  cartRecommendation: undefined,
+  recommendedProductOrder: undefined,
+  isPopular: undefined,
+  isNew: undefined,
+  productStatus: undefined,
+  remark: undefined
+};
+const data = reactive<PageData<BaseForm, BaseQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    productNo: undefined,
+    itemName: undefined,
+    brandId: undefined,
+    productTag: undefined,
+    purchaseNature: undefined,
+    supplierType: undefined,
+    supplierNature: undefined,
+    projectOrg: undefined,
+    topCategoryId: undefined,
+    mediumCategoryId: undefined,
+    bottomCategoryId: undefined,
+    isSelf: 1,
+    productReviewStatus: undefined,
+    productStatus: undefined,
+    lastSeenId: undefined, // 游标分页的lastSeenId
+    way: undefined,
+    params: {}
+  },
+  rules: {
+    productNo: [{ required: true, message: '产品编号不能为空', trigger: 'blur' }],
+    itemName: [{ required: true, message: '项目名称不能为空', trigger: 'blur' }],
+    brandId: [{ required: true, message: '品牌id不能为空', trigger: 'blur' }],
+    topCategoryId: [{ required: true, message: '顶级分类id不能为空', trigger: 'blur' }],
+    mediumCategoryId: [{ required: true, message: '中级分类id不能为空', trigger: 'blur' }],
+    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' }],
+    recommendedProductOrder: [{ required: true, message: '推荐产品顺序不能为空', trigger: 'blur' }],
+    isPopular: [{ required: true, message: '是否热门:1=是,0=否不能为空', trigger: 'blur' }],
+    isNew: [{ required: true, message: '是否新品:1=是,0=否不能为空', trigger: 'blur' }],
+    remark: [{ required: true, message: '备注不能为空', trigger: 'blur' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询产品基础信息列表 */
+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 listBase(params);
+    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);
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 初始化路由参数 */
+const initRouteParams = () => {
+  // 从路由参数中获取筛选条件
+  if (route.query.productReviewStatus) {
+    queryParams.value.productReviewStatus = Number(route.query.productReviewStatus);
+  }
+  if (route.query.brandName) {
+    queryParams.value.brandName = route.query.brandName as string;
+  }
+  if (route.query.bottomCategoryId) {
+    queryParams.value.bottomCategoryId = route.query.bottomCategoryId as string;
+  }
+  if (route.query.isSelf) {
+    queryParams.value.isSelf = Number(route.query.isSelf);
+  }
+  if (route.query.productCategory) {
+    queryParams.value.productCategory = Number(route.query.productCategory);
+  }
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  baseFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+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 = []; // 重置页面历史
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  // 手动重置分类字段
+  queryParams.value.topCategoryId = undefined;
+  queryParams.value.mediumCategoryId = undefined;
+  queryParams.value.bottomCategoryId = undefined;
+  categoryCascadeRef.value?.reset();
+  queryParams.value.lastSeenId = undefined;
+  pageHistory.value = []; // 重置页面历史
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: BaseVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  router.push('/product/base/add');
+};
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: BaseVO) => {
+  const _id = row?.id || ids.value[0];
+  router.push(`/product/base/edit/${_id}`);
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: BaseVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('是否确认删除产品基础信息编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
+  await delBase(_ids);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  // 检查是否有选中的商品
+  if (ids.value.length === 0) {
+    proxy?.$modal.msgWarning('请先选择要导出的商品');
+    return;
+  }
+
+  // 获取选中商品的完整信息
+  const selectedProducts = baseList.value.filter(item => ids.value.includes(item.id));
+
+  if (selectedProducts.length === 0) {
+    proxy?.$modal.msgWarning('未找到选中的商品信息');
+    return;
+  }
+
+  // 转换为 generatePPT 需要的格式
+  const products = selectedProducts.map(item => ({
+    image: item.productImage || item.productImageUrl || '',
+    name: item.itemName || '',
+    code: item.productNo || '',
+    spec: item.specification || item.packagingSpec || '-',
+    price: item.minSellingPrice || item.memberPrice || 0
+  }));
+
+  // 默认模板配置
+  const template = {
+    name: '商品展示方案',
+    title: '商品展示方案',
+    themeColor: '#C00000',
+    itemsPerPage: 1, // 每页1个商品,展示更详细
+    cover: '',
+    logo: ''
+  };
+
+  try {
+    proxy?.$modal.loading('正在生成PPT...');
+    await generatePPT(template, products);
+    proxy?.$modal.closeLoading();
+    proxy?.$modal.msgSuccess('PPT导出成功');
+  } catch (error) {
+    proxy?.$modal.closeLoading();
+    console.error('PPT导出失败:', error);
+    proxy?.$modal.msgError('PPT导出失败');
+  }
+};
+
+/** 查看商品详情 */
+const handleView = (row: BaseVO) => {
+  const url = `https://item.xiaoluwebsite.xyz/item?id=${row.id}`;
+  window.open(url, '_blank');
+};
+
+/** 上下架操作 */
+const handleShelf = async (row: BaseVO) => {
+  const isOnShelf = row.productStatus === 1;
+  const action = isOnShelf ? '下架' : '上架';
+  await proxy?.$modal.confirm(`确认${action}该商品吗?`);
+
+  try {
+    // 上架:状态改为2(上架中),下架:状态改为0(下架)
+    const productStatus = isOnShelf ? 0 : 2;
+    await shelfReview({
+      id: row.id,
+      productStatus: productStatus,
+      shelfComments: `${action}操作`
+    });
+    proxy?.$modal.msgSuccess(`${action}成功`);
+    await getList();
+  } catch (error) {
+    console.error(`${action}失败:`, error);
+    proxy?.$modal.msgError(`${action}失败`);
+  }
+};
+
+
+/** 库存修改弹框 */
+const inventoryDialog = reactive({
+  visible: false,
+  loading: false,
+  submitLoading: false
+});
+
+const inventoryFormRef = ref<ElFormInstance>();
+
+const inventoryForm = reactive<PriceInventoryForm>({
+  productId: undefined,
+  totalInventory: undefined,
+  nowInventory: undefined,
+  virtualInventory: undefined
+});
+
+const inventoryRules = {
+  totalInventory: [{ required: true, message: '总库存不能为空', trigger: 'blur' }],
+  nowInventory: [{ required: true, message: '当前可用库存不能为空', trigger: 'blur' }],
+  virtualInventory: [{ required: true, message: '虚拟库存不能为空', trigger: 'blur' }]
+};
+
+/** 打开库存修改弹框 */
+const handleSupply = async (row: BaseVO) => {
+  inventoryForm.id = row.id;
+  inventoryForm.totalInventory = undefined;
+  inventoryForm.nowInventory = undefined;
+  inventoryForm.virtualInventory = undefined;
+  inventoryDialog.loading = true;
+  inventoryDialog.visible = true;
+  try {
+    const res = await getBase(row.id);
+    if (res.data) {
+      inventoryForm.totalInventory = res.data.totalInventory;
+      inventoryForm.nowInventory = res.data.nowInventory;
+      inventoryForm.virtualInventory = res.data.virtualInventory;
+    }
+  } catch (error) {
+    console.error('获取库存信息失败:', error);
+  } finally {
+    inventoryDialog.loading = false;
+  }
+};
+
+/** 提交库存修改 */
+const submitInventory = async () => {
+  await inventoryFormRef.value?.validate();
+  inventoryDialog.submitLoading = true;
+  try {
+    await updateBase({ ...inventoryForm });
+    proxy?.$modal.msgSuccess('库存修改成功');
+    inventoryDialog.visible = false;
+    await getList();
+  } catch (error) {
+    console.error('库存修改失败:', error);
+    proxy?.$modal.msgError('库存修改失败');
+  } finally {
+    inventoryDialog.submitLoading = false;
+  }
+};
+
+/** 停售操作 */
+const handleDiscontinue = async (row: BaseVO) => {
+  await proxy?.$modal.confirm('确认停售该商品吗?停售后商品将无法正常售卖。');
+
+  try {
+    // 调用停售API,将商品类型改为3(停售商品)
+    await changeProductType({
+      id: row.id,
+      productCategory: 3
+    });
+    proxy?.$modal.msgSuccess('停售成功');
+    await getList();
+  } catch (error) {
+    console.error('停售失败:', error);
+    proxy?.$modal.msgError('停售失败');
+  }
+};
+
+/** 加入自营池操作 */
+const handleAddToSelfPool = async (row: BaseVO) => {
+  await proxy?.$modal.confirm('确认将该商品加入自营池吗?');
+
+  try {
+    await addProductSelf({ productId: row.id, auditStatus: 1 });
+    proxy?.$modal.msgSuccess('加入自营池成功');
+    await getList();
+  } catch (error) {
+    console.error('加入自营池失败:', error);
+    proxy?.$modal.msgError('加入自营池失败');
+  }
+};
+
+/** 加入精品池操作 */
+const handleAddToExquisitePool = async (row: BaseVO) => {
+  await proxy?.$modal.confirm('确认将该商品加入精品池吗?');
+
+  try {
+    await addProductExquisite({ productId: row.id, auditStatus: 1 });
+    proxy?.$modal.msgSuccess('加入精品池成功');
+    await getList();
+  } catch (error) {
+    console.error('加入精品池失败:', error);
+    proxy?.$modal.msgError('加入精品池失败');
+  }
+};
+
+/** 跳转到商品审核页面 */
+const handleGoReview = () => {
+  router.push({
+    path: '/product/base/review',
+    query: {
+      productReviewStatus: 1 // 默认显示待审核的商品
+    }
+  });
+};
+
+/** 查询分类树 */
+const getCategoryTree = async () => {
+  const res = await categoryTree();
+  categoryOptions.value = res.data || [];
+};
+
+/** 加载品牌选项(默认100条) */
+const loadBrandOptions = async (keyword?: string) => {
+  brandLoading.value = true;
+  try {
+    const res = await listBrand({ pageNum: 1, pageSize: 100, brandName: keyword });
+    brandOptions.value = res.rows || [];
+  } catch (error) {
+    console.error('加载品牌列表失败:', error);
+  } finally {
+    brandLoading.value = false;
+  }
+};
+
+/** 品牌远程搜索(防抖) */
+const handleBrandSearch = (query: string) => {
+  if (brandSearchTimer) clearTimeout(brandSearchTimer);
+  brandSearchTimer = setTimeout(() => {
+    loadBrandOptions(query || undefined);
+  }, 300);
+};
+
+/** 获取统计信息 */
+const getStatistics = async () => {
+  try {
+    const res = await getProductStatusCount();
+    if (res.data) {
+      statistics.value = res.data;
+    }
+  } catch (error) {
+    console.error('获取统计信息失败:', error);
+  }
+};
+
+
+onMounted(() => {
+  getList();
+  getStatistics();
+  loadBrandOptions();
+});
+</script>

+ 1 - 1
src/views/product/baseAudit/add.vue

@@ -308,7 +308,7 @@
 
             <!-- 促销标题 -->
             <el-form-item label="促销标题:">
-              <el-input v-model="productForm.packagingSpec" type="textarea" :rows="3" placeholder="请输入促销标题" maxlength="300" show-word-limit />
+              <el-input v-model="productForm.promotionTitle" type="textarea" :rows="3" placeholder="请输入促销标题" maxlength="300" show-word-limit />
             </el-form-item>
 
             <!-- 商品简介 -->

+ 8 - 2
src/views/product/baseAudit/index.vue

@@ -203,7 +203,8 @@
         <el-table-column label="操作" align="center" width="180" fixed="right">
           <template #default="scope">
             <div class="flex flex-wrap gap-1 justify-center">
-              <el-link type="primary" :underline="false" @click="handleView(scope.row)">查看</el-link>
+              <el-link type="primary" :underline="false" @click="handleView(scope.row)">预览</el-link>
+              <el-link type="info" :underline="false" @click="handleViewDetail(scope.row)">查看</el-link>
               <!-- 待提交状态:编辑 + 提交审核 -->
               <template v-if="scope.row.auditStatus === 0">
                 <el-link type="primary" :underline="false" @click="handleUpdate(scope.row)">编辑</el-link>
@@ -612,10 +613,15 @@ const handleExport = async () => {
 
 /** 查看商品详情 */
 const handleView = (row: BaseVO) => {
-  const url = `https://item.yoe365.com:80/itemPreview?id=${row.id}`;
+  const url = `https://item.xiaoluwebsite.xyz/itemPreview?id=${row.id}`;
   window.open(url, '_blank');
 };
 
+/** 查看审核详情页 */
+const handleViewDetail = (row: BaseAuditVO) => {
+  router.push(`/product/baseAudit/view/${row.id}`);
+};
+
 
 /** 上下架操作 */
 const handleShelf = async (row: BaseVO) => {

+ 8 - 8
src/views/product/poolAudit/ProductDetailDrawer.vue

@@ -60,8 +60,8 @@
         <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>
+            <el-tag v-if="String(auditInfo?.applyType) === '0'" type="success">入池申请</el-tag>
+            <el-tag v-else-if="String(auditInfo?.applyType) === '1'" type="warning">出池申请</el-tag>
             <span v-else>-</span>
           </span>
         </div>
@@ -138,7 +138,7 @@
 import { getBase } from '@/api/product/base';
 import { getPoolAudit } from '@/api/product/poolAudit';
 import { getPoolLinkAudit } from '@/api/product/poolLinkAudit';
-import { getCustomerInfo } from '@/api/customer/customerInfo';
+import { getInfo as getProtocolInfo } from '@/api/product/protocolInfo';
 import { getItem } from '@/api/external/item';
 import { getProductCategory } from '@/api/external/productCategory';
 import { ProductCategoryVO } from '@/api/external/productCategory/types';
@@ -148,7 +148,7 @@ 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';
-import { CustomerInfoVO } from '@/api/customer/customerInfo/types';
+import { InfoVO as ProtocolInfoVO } from '@/api/product/protocolInfo/types';
 import { ItemVO } from '@/api/external/item/types';
 import { PoolVO } from '@/api/product/pool/types';
 
@@ -177,7 +177,7 @@ 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 customerInfo = ref<ProtocolInfoVO | null>(null);
 const itemInfo = ref<ItemVO | null>(null);
 const poolInfo = ref<PoolVO | null>(null);
 const thirdCategory = ref<ProductCategoryVO | null>(null);
@@ -191,9 +191,9 @@ const priceForm = reactive({
 const loadExtraInfo = async (type: string, auditData: PoolAuditVO) => {
   extraLoading.value = true;
   try {
-    if (type === '2' && auditData.customerId) {
-      // 协议池 → 查询客户信息
-      const res = await getCustomerInfo(auditData.customerId);
+    if (type === '2' && auditData.protocolId) {
+      // 协议池 → 通过协议信息接口查询客户信息
+      const res = await getProtocolInfo(auditData.protocolId);
       customerInfo.value = res.data || null;
     } else if (type === '3' && auditData.itemId) {
       // 项目池 → 查询项目信息