2 Комити 8ec87ba511 ... 37ee69ecfb

Аутор SHA1 Порука Датум
  肖路 37ee69ecfb Merge remote-tracking branch 'origin/master' into master пре 2 недеља
  肖路 9cfd985067 feat(external): 新增商品导入导出及库存管理功能 пре 2 недеља

+ 30 - 2
src/api/external/product/index.ts

@@ -81,7 +81,7 @@ export const getThirdProductPage = (query?: ProductQuery): AxiosPromise<ThirdPro
  */
 export const getProductByProductId = (productId: string | number): AxiosPromise<ProductVO> => {
   return request({
-    url: '/external/product/getByProductId/' + productId,
+    url: '/external/product/' + productId,
     method: 'get'
   });
 };
@@ -116,4 +116,32 @@ export const batchInsertExternalProduct = (itemId: string | number, boList: any[
     params: { itemId },
     data: boList
   });
-};
+};
+
+/**
+ * 下载导入模板
+ */
+export const importTemplate = () => {
+  return request({
+    url: '/external/product/importTemplate',
+    method: 'post',
+    responseType: 'blob'
+  });
+};
+
+/**
+ * 导入商品数据
+ * @param file 导入文件
+ */
+export const importData = (file: File) => {
+  const formData = new FormData();
+  formData.append('file', file);
+  return request({
+    url: '/external/product/importData',
+    method: 'post',
+    data: formData,
+    headers: {
+      'Content-Type': 'multipart/form-data'
+    }
+  });
+};

+ 9 - 0
src/api/external/product/types.ts

@@ -87,6 +87,10 @@ export interface ProductForm extends BaseEntity {
    */
   remark?: string;
 
+  /**
+   * 总库存
+   */
+  totalInventory?: number;
 }
 
 export interface ProductQuery extends PageQuery {
@@ -418,6 +422,11 @@ export interface ThirdProductVO {
    * 第三方价格
    */
   externalPrice?: number;
+
+  /**
+   * 总库存
+   */
+  totalInventory?: number;
 }
 
 

+ 19 - 19
src/views/external/item/index.vue

@@ -26,14 +26,14 @@
       <!-- 项目卡片网格 -->
       <div v-loading="loading" class="project-grid-wrapper">
         <el-row v-if="itemList.length > 0" :gutter="24" class="project-grid">
-          <el-col 
-            v-for="item in itemList" 
-            :key="item.id" 
+          <el-col
+            v-for="item in itemList"
+            :key="item.id"
             :xs="24" :sm="12" :md="12" :lg="8" :xl="6"
             class="grid-item"
           >
-            <div 
-              class="project-card" 
+            <div
+              class="project-card"
               :class="{ 'is-selected': selectedId === item.id }"
               @click="selectProject(item.id)"
               @dblclick="handleEnterProject(item)"
@@ -59,7 +59,7 @@
                     </template>
                   </el-image>
                 </div>
-                
+
                 <el-tooltip
                   effect="dark"
                   :content="item.itemName"
@@ -68,7 +68,7 @@
                 >
                   <h3 class="project-name">{{ item.itemName }}</h3>
                 </el-tooltip>
-                
+
                 <div class="stats-grid">
                   <div class="stat-item">
                     <span class="label">商品数</span>
@@ -121,8 +121,8 @@
 
         <!-- 空状态 -->
         <div v-else-if="!loading" class="empty-container">
-          <el-empty 
-            description="暂无相关项目信息" 
+          <el-empty
+            description="暂无相关项目信息"
             :image-size="200"
           >
             <template #extra>
@@ -148,18 +148,18 @@
     </main>
 
     <!-- 添加或修改第三方对接项目管理对话框 -->
-    <el-dialog 
-      :title="dialog.title" 
-      v-model="dialog.visible" 
-      width="550px" 
+    <el-dialog
+      :title="dialog.title"
+      v-model="dialog.visible"
+      width="600px"
       append-to-body
       destroy-on-close
       class="project-dialog"
     >
-      <el-form 
-        ref="itemFormRef" 
-        :model="form" 
-        :rules="rules" 
+      <el-form
+        ref="itemFormRef"
+        :model="form"
+        :rules="rules"
         label-width="100px"
         label-position="right"
         style="padding: 20px 40px 0 20px"
@@ -548,7 +548,7 @@ onMounted(() => {
       .stat-item {
         display: flex;
         flex-direction: column;
-        
+
         .label {
           font-size: 11px;
           color: #909399;
@@ -591,7 +591,7 @@ onMounted(() => {
     background: #fafafa;
     display: flex;
     justify-content: space-around;
-    
+
     .el-button {
       font-size: 13px;
     }

+ 261 - 29
src/views/external/product/index.vue

@@ -20,6 +20,8 @@
               <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
               <el-button icon="Refresh" @click="resetQuery">重置</el-button>
               <el-button type="success" :disabled="multiple" @click="handleBatchPush">批量推送</el-button>
+              <el-button type="info" icon="Upload" @click="handleImport">导入</el-button>
+              <el-button type="warning" icon="Download" @click="handleImportTemplate">下载模板</el-button>
             </el-form-item>
           </el-form>
         </el-card>
@@ -55,13 +57,8 @@
         <el-table-column label="平档价" align="center" prop="standardPrice" width="100" />
         <el-table-column label="供应价" align="center" prop="purchasePrice" width="100" />
         <el-table-column label="第三方售价" align="center" prop="externalPrice" width="100" />
-        <el-table-column label="限定库存" align="center" prop="availableStock" width="100" />
-        <el-table-column label="可用库存" align="center" prop="availableStock" width="100" />
-        <el-table-column label="总订单" align="center" width="100">
-          <template #default="scope">
-            <span>0</span>
-          </template>
-        </el-table-column>
+        <el-table-column label="总库存" align="center" prop="totalInventory" width="100" />
+
         <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>
@@ -80,6 +77,9 @@
             <el-tooltip content="编辑" placement="top">
               <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" >编辑</el-button>
             </el-tooltip>
+            <el-tooltip content="编辑库存" placement="top">
+              <el-button link type="success" icon="Edit" @click="handleEditInventory(scope.row)">库存</el-button>
+            </el-tooltip>
             <el-tooltip content="上下架" placement="top">
               <el-button link type="primary" @click="handleToggleStatus(scope.row)">{{ scope.row.productStatus == '1' ? '下架' : '上架' }}</el-button>
             </el-tooltip>
@@ -132,18 +132,28 @@
           />
         </el-form-item>
         <el-form-item label="第三方平台售价:" prop="externalPrice">
-          <el-input-number 
-            v-model="form.externalPrice" 
-            :precision="2" 
-            :min="0" 
-            :controls="false" 
+          <el-input-number
+            v-model="form.externalPrice"
+            :precision="2"
+            :min="0"
+            :controls="false"
             placeholder="请输入第三方平台售价"
-            style="width: 100%" 
+            style="width: 100%"
           />
         </el-form-item>
         <el-form-item label="备注:" prop="remark">
           <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
         </el-form-item>
+        <el-form-item label="总库存:" prop="totalInventory">
+          <el-input-number
+            v-model="form.totalInventory"
+            :precision="0"
+            :min="0"
+            :controls="false"
+            placeholder="请输入总库存"
+            style="width: 100%"
+          />
+        </el-form-item>
       </el-form>
       <template #footer>
         <div class="dialog-footer">
@@ -152,17 +162,81 @@
         </div>
       </template>
     </el-dialog>
+
+    <!-- 导入对话框 -->
+    <el-dialog v-model="importDialog.visible" :title="importDialog.title" width="400px" append-to-body>
+      <el-upload
+        ref="uploadRef"
+        drag
+        :limit="1"
+        :accept="importDialog.fileType"
+        :before-upload="handleBeforeUpload"
+        :on-exceed="handleExceed"
+        :http-request="handleFileUpload"
+      >
+        <el-icon class="el-icon--upload">
+          <upload-filled />
+        </el-icon>
+        <div class="el-upload__text">
+          将文件拖到此处,或<em>点击上传</em>
+        </div>
+        <template #tip>
+          <div class="el-upload__tip text-center">
+            <span>仅支持 {{ importDialog.fileType }} 格式文件</span>
+          </div>
+        </template>
+      </el-upload>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="warning" @click="importDialog.visible = false">取 消</el-button>
+          <el-button type="primary" @click="confirmImport">确 定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 库存编辑对话框 -->
+    <el-dialog v-model="inventoryDialog.visible" :title="inventoryDialog.title" width="500px" append-to-body>
+      <el-form ref="inventoryFormRef" :model="inventoryForm" :rules="inventoryRules" label-width="100px">
+        <el-form-item label="商品编号:">
+          <el-input v-model="inventoryForm.productNo" disabled />
+        </el-form-item>
+        <el-form-item label="商品名称:">
+          <el-input v-model="inventoryForm.itemName" disabled />
+        </el-form-item>
+        <el-form-item label="当前库存:" prop="currentInventory">
+          <el-input-number
+            v-model="inventoryForm.currentInventory"
+            :precision="0"
+            :min="0"
+            :controls="true"
+            placeholder="请输入当前库存"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="备注:" prop="remark">
+          <el-input v-model="inventoryForm.remark" type="textarea" :rows="3" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="warning" @click="inventoryDialog.visible = false">取 消</el-button>
+          <el-button type="primary" :loading="inventoryLoading" @click="submitInventoryForm">确 定</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="Product" lang="ts">
-import { getThirdProductPage, getProduct, updateProduct, shelfReview, batchPushProduct } from '@/api/external/product';
+import { getThirdProductPage, getProduct, updateProduct, shelfReview, batchPushProduct, importData, importTemplate } from '@/api/external/product';
 import { ThirdProductVO, ProductQuery, ProductVO, ProductForm } from '@/api/external/product/types';
 import { getProductCategoryTree } from '@/api/external/productCategory';
 import { ProductCategoryVO } from '@/api/external/productCategory/types';
 import { listPushPoolLog } from '@/api/external/pushPoolLog';
 import { PushPoolLogVO } from '@/api/external/pushPoolLog/types';
 import cache from '@/plugins/cache';
+import { UploadFilled } from '@element-plus/icons-vue';
+import type { UploadInstance, UploadRawFile } from 'element-plus';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
@@ -180,6 +254,8 @@ const onShelfCount = ref(0);
 const queryFormRef = ref<ElFormInstance>();
 const productFormRef = ref<ElFormInstance>();
 const productTableRef = ref();
+const uploadRef = ref<UploadInstance>();
+const inventoryFormRef = ref<ElFormInstance>();
 
 const dialog = reactive<DialogOption>({
   visible: false,
@@ -193,6 +269,20 @@ const logDrawer = reactive({
   data: [] as PushPoolLogVO[]
 });
 
+// 导入对话框数据
+const importDialog = reactive({
+  visible: false,
+  title: '导入商品数据',
+  fileType: '.xlsx, .xls',
+  selectedFile: null as File | null
+});
+
+// 库存编辑对话框数据
+const inventoryDialog = reactive({
+  visible: false,
+  title: '编辑库存'
+});
+
 // 编辑模式标识
 const isEditMode = ref(false);
 
@@ -205,7 +295,8 @@ const data = reactive<PageData<ProductForm, ProductQuery>>({
     externalCategoryId: undefined,
     externalPrice: 0,
     pushStatus: 0,
-    remark: undefined
+    remark: undefined,
+    totalInventory: 0
   },
   queryParams: {
     pageNum: 1,
@@ -317,7 +408,8 @@ const reset = () => {
     externalCategoryId: undefined,
     externalPrice: 0,
     pushStatus: 0,
-    remark: undefined
+    remark: undefined,
+    totalInventory: 0
   };
   productFormRef.value?.resetFields();
 }
@@ -353,17 +445,17 @@ const handleUpdate = async (row?: ThirdProductVO) => {
   reset();
   isEditMode.value = true;
   dialog.title = '编辑对接信息';
-  
+
   try {
     // 确保分类数据已加载
     if (externalCategoryList.value.length === 0) {
       await initCategoryData();
     }
-    
+
     // 通过商品ID获取对接信息
     const res = await getProduct(row!.id);
     const productInfo = res.data;
-    
+
     form.value = {
       id: productInfo.id,
       productId: productInfo.productId,
@@ -372,9 +464,10 @@ const handleUpdate = async (row?: ThirdProductVO) => {
       externalCategoryId: productInfo.externalCategoryId,
       externalPrice: productInfo.externalPrice,
       pushStatus: productInfo.pushStatus,
-      remark: productInfo.remark
+      remark: productInfo.remark,
+      totalInventory: productInfo.totalInventory || 0
     };
-    
+
     dialog.visible = true;
   } catch (error) {
     console.error('获取对接信息失败:', error);
@@ -385,7 +478,7 @@ const handleUpdate = async (row?: ThirdProductVO) => {
 /** 提交按钮 */
 const submitForm = async () => {
   if (!productFormRef.value) return;
-  
+
   await productFormRef.value.validate(async (valid) => {
     if (valid) {
       buttonLoading.value = true;
@@ -399,7 +492,7 @@ const submitForm = async () => {
           proxy?.$modal.msgWarning('暂不支持新增');
           return;
         }
-        
+
         dialog.visible = false;
         await getList();
       } catch (error) {
@@ -421,18 +514,18 @@ const handleDelete = async (row?: ThirdProductVO) => {
 const handleToggleStatus = async (row: ThirdProductVO) => {
   const action = row.productStatus == '1' ? '下架' : '上架';
   const newStatus = row.productStatus == '1' ? '0' : '1';
-  
+
   try {
     await proxy?.$modal.confirm(`确认${action}该商品吗?`);
-    
+
     // 调用上下架接口
     await shelfReview({
       id: row.id,
       productStatus: newStatus
     });
-    
+
     proxy?.$modal.msgSuccess(`${action}成功`);
-    
+
     // 刷新列表
     await getList();
   } catch (error) {
@@ -473,7 +566,7 @@ const handleViewLog = async (row: ThirdProductVO) => {
   logDrawer.visible = true;
   logDrawer.loading = true;
   logDrawer.data = [];
-  
+
   try {
     // 获取项目ID
     const itemId = queryParams.value.itemId;
@@ -482,7 +575,7 @@ const handleViewLog = async (row: ThirdProductVO) => {
       logDrawer.visible = false;
       return;
     }
-    
+
     // 调用列表接口,传入项目id和商品池id
     const res = await listPushPoolLog({ itemId, productId: row.productId, pageNum: 1, pageSize: 100 });
     logDrawer.data = res.rows || [];
@@ -501,6 +594,145 @@ const handleExport = () => {
   }, `product_${new Date().getTime()}.xlsx`)
 }
 
+/** 导入按钮操作 */
+const handleImport = () => {
+  importDialog.visible = true;
+  importDialog.selectedFile = null;
+  uploadRef.value?.clearFiles();
+}
+
+/** 下载导入模板 */
+const handleImportTemplate = async () => {
+  try {
+    const response = await importTemplate();
+    const blob = new Blob([response], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
+    const url = window.URL.createObjectURL(blob);
+    const link = document.createElement('a');
+    link.href = url;
+    link.download = `对外部推送商品模板_${new Date().getTime()}.xlsx`;
+    link.click();
+    window.URL.revokeObjectURL(url);
+    proxy?.$modal.msgSuccess('模板下载成功');
+  } catch (error) {
+    console.error('下载模板失败:', error);
+    proxy?.$modal.msgError('下载模板失败');
+  }
+}
+
+/** 文件上传前校验 */
+const handleBeforeUpload = (file: UploadRawFile) => {
+  const isExcel = file.type === 'application/vnd.ms-excel' ||
+                  file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
+  if (!isExcel) {
+    proxy?.$modal.msgError('只能上传 Excel 文件格式!');
+    return false;
+  }
+  importDialog.selectedFile = file;
+  return false; // 阻止自动上传
+}
+
+/** 文件超出数量限制 */
+const handleExceed = () => {
+  proxy?.$modal.msgWarning('只能上传一个文件');
+}
+
+/** 自定义文件上传处理 */
+const handleFileUpload = async () => {
+  if (!importDialog.selectedFile) {
+    proxy?.$modal.msgWarning('请选择要导入的文件');
+    return;
+  }
+
+  try {
+    const res = await importData(importDialog.selectedFile);
+    proxy?.$modal.msgSuccess(res.msg || '导入成功');
+    importDialog.visible = false;
+    uploadRef.value?.clearFiles();
+    importDialog.selectedFile = null;
+    await getList();
+  } catch (error) {
+    console.error('导入失败:', error);
+    proxy?.$modal.msgError('导入失败');
+  }
+}
+
+/** 确认导入 */
+const confirmImport = () => {
+  if (!importDialog.selectedFile) {
+    proxy?.$modal.msgWarning('请先选择要导入的文件');
+    return;
+  }
+  handleFileUpload();
+}
+
+/** 编辑库存操作 */
+const handleEditInventory = async (row: ThirdProductVO) => {
+  // 重置表单
+  inventoryForm.id = row.id;
+  inventoryForm.productNo = row.productNo;
+  inventoryForm.itemName = row.itemName;
+  inventoryForm.currentInventory = row.availableStock || 0;
+  inventoryForm.remark = undefined;
+
+  inventoryDialog.title = '编辑库存';
+  inventoryDialog.visible = true;
+}
+
+/** 提交库存表单 */
+const submitInventoryForm = async () => {
+  if (!inventoryFormRef.value) return;
+
+  await inventoryFormRef.value.validate(async (valid) => {
+    if (valid) {
+      inventoryLoading.value = true;
+      try {
+        // 调用编辑接口,更新库存
+        const updateData = {
+          id: inventoryForm.id,
+          totalInventory: inventoryForm.currentInventory,
+          remark: inventoryForm.remark
+        };
+
+        await updateProduct(updateData);
+        proxy?.$modal.msgSuccess('库存修改成功');
+        inventoryDialog.visible = false;
+        await getList();
+      } catch (error) {
+        console.error('修改库存失败:', error);
+        proxy?.$modal.msgError('修改库存失败');
+      } finally {
+        inventoryLoading.value = false;
+      }
+    }
+  });
+}
+
+// 库存编辑表单数据
+interface InventoryForm {
+  id?: string | number;
+  productNo?: string;
+  itemName?: string;
+  currentInventory?: number;
+  remark?: string;
+}
+
+const inventoryForm = reactive<InventoryForm>({
+  id: undefined,
+  productNo: undefined,
+  itemName: undefined,
+  currentInventory: 0,
+  remark: undefined
+});
+
+const inventoryRules = reactive({
+  currentInventory: [
+    { required: true, message: '请输入当前库存', trigger: 'blur' }
+  ]
+});
+
+// 库存编辑加载状态
+const inventoryLoading = ref(false);
+
 onMounted(() => {
   // 从缓存获取项目ID并初始化
   initProjectId();

+ 201 - 0
src/views/external/productCategory/discountRate.vue

@@ -0,0 +1,201 @@
+<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" :inline="true">
+            <el-form-item label="分类名称" prop="categoryName">
+              <el-input v-model="queryParams.categoryName" placeholder="请输入分类名称" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="hover">
+      <el-table
+        ref="productCategoryTableRef"
+        v-loading="loading"
+        :data="productCategoryList"
+        row-key="id"
+        border
+      >
+        <el-table-column prop="parentCategoryName" label="分类名称" min-width="600">
+        <template #default="scope">
+            <span >{{ scope.row.parentCategoryName +"-"+ scope.row.categoryName }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="discountRate" align="center" label="折扣率" width="600">
+          <template #default="scope">
+            <span v-if="scope.row.discountRate">{{ (scope.row.discountRate * 100).toFixed(2) }}%</span>
+            <span v-else class="text-gray-400">未设置</span>
+          </template>
+        </el-table-column>
+        <el-table-column fixed="right" align="center" label="操作" width="300">
+          <template #default="scope">
+            <el-tooltip content="修改折扣率" placement="top">
+              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" />
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+
+    <!-- 修改折扣率对话框 -->
+    <el-dialog v-model="dialog.visible" title="修改折扣率" destroy-on-close append-to-body width="500px">
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="分类名称">
+          <el-input v-model="form.categoryName" disabled />
+        </el-form-item>
+        <el-form-item label="折扣率" prop="discountRate">
+          <el-input-number
+            v-model="form.discountRate"
+            :precision="4"
+            :min="0"
+            :max="1"
+            :step="0.01"
+            :controls="false"
+            style="width: 200px"
+          />
+          <span class="ml-2 text-gray-500">{{ form.discountRate ? (form.discountRate * 100).toFixed(2) + '%' : '0%' }}</span>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">确 认</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="ProductCategoryDiscountRate" lang="ts">
+import { listProductCategory, getProductCategory, updateProductCategory } from '@/api/external/productCategory';
+import { ProductCategoryVO, ProductCategoryQuery, ProductCategoryDiscountForm } from '@/api/external/productCategory/types';
+import cache from '@/plugins/cache';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const productCategoryList = ref<ProductCategoryVO[]>([]);
+const loading = ref(true);
+const showSearch = ref(true);
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const productCategoryTableRef = ref<ElTableInstance>();
+const queryFormRef = ref<ElFormInstance>();
+const formRef = ref<ElFormInstance>();
+
+const initFormData: ProductCategoryDiscountForm = {
+  id: undefined,
+  itemId: undefined,
+  categoryName: undefined,
+  discountRate: undefined
+};
+
+const data = reactive<PageData<ProductCategoryDiscountForm, Partial<ProductCategoryQuery>>>({
+  form: { ...initFormData },
+  queryParams: {
+    categoryName: undefined,
+    itemId: undefined
+  },
+  rules: {
+    discountRate: [
+      { required: true, message: '折扣率不能为空', trigger: 'blur' }
+    ]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询产品分类列表(只加载三级分类) */
+const getList = async () => {
+  loading.value = true;
+  // 只加载三级分类(classLevel = 3)
+  const params = {
+    ...queryParams.value,
+    classLevel: 3
+  };
+  const res = await listProductCategory(params);
+  const responseData = (res as any).rows || res.data || [];
+  productCategoryList.value = responseData;
+  loading.value = false;
+};
+
+/** 初始化项目Key(从缓存获取) */
+const initProjectKey = () => {
+  const currentProject = cache.local.getJSON('currentProject');
+  if (currentProject && currentProject.id) {
+    queryParams.value.itemId = currentProject.id;
+  }
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  formRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 修改按钮操作 */
+const handleUpdate = async (row: ProductCategoryVO) => {
+  reset();
+  const res = await getProductCategory(row.id, queryParams.value.itemId?.toString());
+  form.value = {
+    id: res.data.id,
+    itemId: res.data.itemId,
+    categoryName: res.data.categoryName,
+    discountRate: res.data.discountRate ?? undefined
+  };
+  dialog.visible = true;
+  dialog.title = '修改折扣率';
+};
+
+/** 提交按钮 */
+const submitForm = () => {
+  formRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      await updateProductCategory({
+        id: form.value.id,
+        itemId: form.value.itemId,
+        discountRate: form.value.discountRate
+      } as any);
+      proxy?.$modal.msgSuccess('修改成功');
+      dialog.visible = false;
+      await getList();
+    }
+  });
+};
+
+onMounted(() => {
+  // 从缓存获取项目Key并初始化
+  initProjectKey();
+  if (queryParams.value.itemId) {
+    getList();
+  }
+});
+</script>

+ 48 - 15
src/views/product/base/index.vue

@@ -101,6 +101,12 @@
     <!-- 对接弹框 -->
     <el-dialog v-model="connectDialog.visible" title="选择第三方产品分类" width="600px" append-to-body @close="cancelConnect">
       <el-form ref="connectFormRef" :model="connectForm" :rules="connectRules" label-width="130px">
+        <el-form-item label="价格模式:" prop="pricingRule">
+          <el-radio-group v-model="connectForm.pricingRule" @change="handlePriceModeChange">
+            <el-radio label="0" >一品一率</el-radio>
+            <el-radio label="1">折扣率</el-radio>
+          </el-radio-group>
+        </el-form-item>
         <el-form-item label="第三方产品分类:" prop="externalCategoryId">
           <el-cascader
             v-model="connectForm.externalCategoryId"
@@ -112,11 +118,11 @@
             @change="handleCategoryChange"
           />
         </el-form-item>
-        <el-form-item label="折扣率:">
+        <el-form-item v-if="connectForm.pricingRule === '1'" label="折扣率:">
           <el-text type="info">{{ discountRateDisplay }}</el-text>
         </el-form-item>
         <el-form-item label="第三方平台售价:" prop="externalPrice">
-          <el-input-number v-model="connectForm.externalPrice" :precision="2" :min="0" :controls="false" style="width: 100%" />
+          <el-input-number v-model="connectForm.externalPrice" :precision="2" :min="0" :controls="false" :disabled="connectForm.pricingRule === '1'" style="width: 100%" />
         </el-form-item>
         <el-form-item label="市场价:">
           <el-text>{{ connectForm.marketPrice }}</el-text>
@@ -136,7 +142,6 @@
         <div class="dialog-footer">
           <el-button type="warning" @click="cancelConnect">返回</el-button>
           <el-button type="success" @click="submitConnect" :loading="connectLoading">
-            <el-icon><Check /></el-icon>
             确认
           </el-button>
         </div>
@@ -199,7 +204,7 @@ const connectDialog = reactive({
 
 // 对接表单
 const connectForm = ref({
-  id: undefined as string | number | undefined, // 对接记录ID(编辑时使用)
+  id: undefined as string | number | undefined, // 对接记录 ID(编辑时使用)
   productId: undefined as string | number | undefined,
   externalCategoryId: undefined as string | number | undefined,
   externalPrice: 0,
@@ -207,7 +212,8 @@ const connectForm = ref({
   memberPrice: 0,
   minSellingPrice: 0,
   minOrderQuantity: 1,
-  discountRate: 0 // 折扣率
+  discountRate: 0, // 折扣率
+  pricingRule: '0' // 价格模式:0=一品一率,1=折扣率
 });
 
 // 外部分类列表(懒加载模式只需要初始化为空数组)
@@ -226,7 +232,7 @@ const cascaderProps = {
       // level 0 表示根节点,加载一级分类
       // level > 0 表示子节点,根据父节点ID加载子分类
       const parentId = level === 0 ? 0 : value;
-      
+
       // 从缓存中获取项目ID
       const currentProject = cache.local.getJSON('currentProject');
       const itemId = currentProject?.id;
@@ -521,7 +527,7 @@ const handleView = (row: BaseVO) => {
 
 /** 对接操作 */
 const handleConnect = async (row: BaseVO) => {
-  // 重置表单
+  // 初始化表单数据,默认一品一率模式
   connectForm.value = {
     id: undefined,
     productId: row.id,
@@ -531,7 +537,8 @@ const handleConnect = async (row: BaseVO) => {
     memberPrice: row.memberPrice || 0,
     minSellingPrice: row.minSellingPrice || 0,
     minOrderQuantity: row.minOrderQuantity || 1,
-    discountRate: 0
+    discountRate: 0,
+    pricingRule: '0' // 默认一品一率
   };
 
   // 查询是否已有对接信息
@@ -543,7 +550,7 @@ const handleConnect = async (row: BaseVO) => {
       connectForm.value.id = existData.id;
       connectForm.value.externalCategoryId = existData.externalCategoryId;
       connectForm.value.externalPrice = existData.externalPrice || 0;
-      
+
       // 如果有分类,获取折扣率
       if (existData.externalCategoryId) {
         const currentProject = cache.local.getJSON('currentProject');
@@ -553,6 +560,15 @@ const handleConnect = async (row: BaseVO) => {
           connectForm.value.discountRate = categoryRes.data.discountRate || 0;
         }
       }
+
+      // 根据是否有自定义价格判断价格模式
+      // 如果 externalPrice 为 0 或者等于计算出的折扣价,则认为是折扣率模式
+      const calculatedDiscountPrice = Number((connectForm.value.memberPrice * connectForm.value.discountRate).toFixed(2));
+      if (connectForm.value.externalPrice === 0 || connectForm.value.externalPrice === calculatedDiscountPrice) {
+        connectForm.value.pricingRule = '1';
+      } else {
+        connectForm.value.pricingRule = '0';
+      }
     }
   } catch (error) {
     console.log('暂无对接信息,将新增');
@@ -577,22 +593,38 @@ const handleCategoryChange = async (value: string | number) => {
     const itemId = currentProject?.id;
     const res = await getProductCategory(value, itemId);
     const category = res.data;
-    
+
     // 设置折扣率
     const discountRate = category?.discountRate || 0;
     connectForm.value.discountRate = discountRate;
-    
-    // 第三方价格默认为平台价 * 折扣率
-    const defaultPrice = Number((connectForm.value.memberPrice * discountRate).toFixed(2));
-    connectForm.value.externalPrice = defaultPrice;
+
+    // 只有在折扣率模式下才自动计算价格
+    if (connectForm.value.pricingRule === '1') {
+      // 第三方价格默认为平台价 * 折扣率
+      const defaultPrice = Number((connectForm.value.memberPrice * discountRate).toFixed(2));
+      connectForm.value.externalPrice = defaultPrice;
+    }
   } catch (error) {
     console.error('获取分类详情失败:', error);
     // 如果获取失败,默认使用会员价作为第三方价格
-    connectForm.value.externalPrice = connectForm.value.memberPrice;
+    if (connectForm.value.pricingRule === '1') {
+      connectForm.value.externalPrice = connectForm.value.memberPrice;
+    }
     connectForm.value.discountRate = 0;
   }
 };
 
+/** 价格模式变化处理 */
+const handlePriceModeChange = (value: '1' | '0') => {
+  if (value === '1') {
+    // 切换到折扣率模式,自动计算价格
+    const discountRate = connectForm.value.discountRate || 0;
+    const defaultPrice = Number((connectForm.value.memberPrice * discountRate).toFixed(2));
+    connectForm.value.externalPrice = defaultPrice;
+  }
+  // 切换到一品一率模式,不做任何操作,允许用户手动输入
+};
+
 /** 取消对接 */
 const cancelConnect = () => {
   connectDialog.visible = false;
@@ -611,6 +643,7 @@ const submitConnect = async () => {
           id: connectForm.value.id,
           productId: connectForm.value.productId,
           externalCategoryId: connectForm.value.externalCategoryId,
+          pricingRule : connectForm.value.pricingRule,
           externalPrice: connectForm.value.externalPrice,
           minOrderQuantity: connectForm.value.minOrderQuantity,
           pushStatus: 0 // 0=未推送