Jelajahi Sumber

feat(supplier): 重构合约供货编辑页面并优化供应商资质管理

- 移除页面头部返回按钮和基本信息表单
- 添加商品报价表格支持导入导出功能
- 新增下载模板、导入商品对话框组件
- 实现商品列表分页展示和删除确认功能
- 集成商品报价导入API和模板下载功能
- 重构商品对话框组件支持实时供应价输入
- 添加供应价校验确保不大于官网价
- 优化批量添加商品报价处理逻辑
- 更新API接口使用新的合同产品管理服务
- 添加供应商资质状态管理和提交作废功能
- 优化资质列表显示状态标签和操作按钮
- 移除合约供货新增按钮统一管理入口
- 优化商品分类显示格式和列宽配置
肖路 1 hari lalu
induk
melakukan
b1d2e9935e

+ 54 - 0
src/api/product/contracproduct/index.ts

@@ -1,5 +1,6 @@
 import request from '@/utils/request';
 import { AxiosPromise } from 'axios';
+import { BaseVO, BaseForm, BaseQuery, StatusCountVo } from '@/api/product/base/types';
 import { ContracproductVO, ContracproductForm, ContracproductQuery } from '@/api/product/contracproduct/types';
 
 /**
@@ -61,3 +62,56 @@ export const delContracproduct = (id: string | number | Array<string | number>)
     method: 'delete'
   });
 };
+/**
+ * 查询产品基础信息列表
+ * @param query
+ * @returns {*}
+ */
+
+export const getContractProductList = (query?: BaseQuery): AxiosPromise<BaseVO[]> => {
+  return request({
+    url: '/product/contracproduct/getContractProductList',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 删除合同产品关联
+ * @param id
+ */
+export const delContracproductBySupplier = (productId: string | number, supplierId: string | number) => {
+  return request({
+    url: '/product/contracproduct/delContracproductBySupplier',
+    params: { productId: productId, supplierId: supplierId },
+    method: 'delete'
+  });
+};
+
+/**
+ * 下载导入模板
+ */
+export const importTemplate = () => {
+  return request({
+    url: '/product/contracproduct/importTemplate',
+    method: 'post',
+    responseType: 'blob'
+  });
+};
+
+/**
+ * 导入合同产品数据
+ * @param file 导入文件
+ */
+export const importData = (file: File) => {
+  const formData = new FormData();
+  formData.append('file', file);
+  return request({
+    url: '/product/contracproduct/importData',
+    method: 'post',
+    data: formData,
+    headers: {
+      'Content-Type': 'multipart/form-data'
+    }
+  });
+};

+ 22 - 0
src/api/supplier/qualification/index.ts

@@ -61,3 +61,25 @@ export const delQualification = (id: string | number | Array<string | number>) =
     method: 'delete'
   });
 };
+
+/**
+ * 提交资质(待提交→生效)
+ * @param id
+ */
+export const submitQualification = (id: string | number) => {
+  return request({
+    url: '/customer/qualification/submit/' + id,
+    method: 'put'
+  });
+};
+
+/**
+ * 作废资质(生效→作废)
+ * @param id
+ */
+export const voidQualification = (id: string | number) => {
+  return request({
+    url: '/customer/qualification/void/' + id,
+    method: 'put'
+  });
+};

+ 10 - 0
src/api/supplier/qualification/types.ts

@@ -54,6 +54,11 @@ export interface QualificationVO {
    */
   attachmentName: string;
 
+  /**
+   * 资质状态(0=待提交 1=生效 2=作废)
+   */
+  qualificationStatus: string | number;
+
   /**
    * 备注
    */
@@ -117,6 +122,11 @@ export interface QualificationForm extends BaseEntity {
    */
   attachmentName?: string;
 
+  /**
+   * 资质状态(0=待提交 1=生效 2=作废)
+   */
+  qualificationStatus?: string | number;
+
   /**
    * 备注
    */

+ 107 - 5
src/views/supplier/aptitude/index.vue

@@ -19,6 +19,14 @@
 
       <el-table-column prop="issuingAuthority" label="发证机构" align="center" width="120" />
 
+      <el-table-column label="资质状态" align="center" width="100">
+        <template #default="scope">
+          <el-tag :type="getStatusTagType(scope.row.qualificationStatus)">
+            {{ getStatusText(scope.row.qualificationStatus) }}
+          </el-tag>
+        </template>
+      </el-table-column>
+
       <el-table-column label="资质到期日" align="center" width="150">
         <template #default="scope">
           <span v-if="scope.row.isLongValid == 1"> {{ formatDate(scope.row.endDate) }} 长期有效 </span>
@@ -37,20 +45,28 @@
         </template>
       </el-table-column>
 
-      <el-table-column label="操作" align="center" width="200" fixed="right">
+      <el-table-column label="操作" align="center" width="250" fixed="right">
         <template #default="scope">
           <el-button link type="primary" @click="handleView(scope.row)">查看</el-button>
 
-          <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
+          <template v-if="scope.row.qualificationStatus == 0 || scope.row.qualificationStatus == null">
+            <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
 
-          <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
+            <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
+
+            <el-button link type="success" @click="handleSubmitQualification(scope.row)">提交</el-button>
+          </template>
+
+          <el-button v-if="scope.row.qualificationStatus == 1" link type="warning" @click="handleVoidQualification(scope.row)">
+            作废
+          </el-button>
         </template>
       </el-table-column>
     </el-table>
 
     <!-- 空状态 -->
 
-    <div v-if="qualificationList.length === 0 && !loading" class="empty-state">暂无资质信息</div>
+
 
     <!-- 分页 -->
 
@@ -100,6 +116,17 @@
           </el-col>
         </el-row>
 
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="资质状态">
+              <el-tag v-if="formData.id" :type="getStatusTagType(formData.qualificationStatus)">
+                {{ getStatusText(formData.qualificationStatus) }}
+              </el-tag>
+              <el-tag v-else type="info">待提交</el-tag>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
         <el-row :gutter="20">
           <el-col :span="24">
             <el-form-item label="资质到期日">
@@ -190,6 +217,16 @@
           </el-col>
         </el-row>
 
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="资质状态">
+              <el-tag :type="getStatusTagType(viewData.qualificationStatus)">
+                {{ getStatusText(viewData.qualificationStatus) }}
+              </el-tag>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
         <el-row :gutter="20">
           <el-col :span="24">
             <el-form-item label="资质到期日">
@@ -242,7 +279,7 @@
 import { ref, onMounted } from 'vue';
 import { useRoute } from 'vue-router';
 import { ElMessage, ElMessageBox } from 'element-plus';
-import { listQualification, addQualification, updateQualification, delQualification } from '@/api/supplier/qualification';
+import { listQualification, addQualification, updateQualification, delQualification, submitQualification, voidQualification } from '@/api/supplier/qualification';
 import type { QualificationVO, QualificationForm, QualificationQuery } from '@/api/supplier/qualification/types';
 import { listByIds } from '@/api/system/oss';
 import { getInfo, updateInfo } from '@/api/supplier/info';
@@ -323,6 +360,8 @@ const formData = ref<QualificationForm>({
 
   attachmentName: '',
 
+  qualificationStatus: 0,
+
   remark: ''
 });
 
@@ -662,6 +701,7 @@ const syncSupplierInfo = async () => {
 
 const resetForm = () => {
   formData.value = {
+    supplierId: userStore.supplierId,
     qualificationName: '',
 
     qualificationLevel: '',
@@ -680,6 +720,8 @@ const resetForm = () => {
 
     attachmentName: '',
 
+    qualificationStatus: 0,
+
     remark: ''
   };
 
@@ -755,6 +797,66 @@ const getSupplierInfo = async () => {
     console.error('获取供应商基础信息失败:', e);
   }
 };
+
+/** 获取状态文本 */
+const getStatusText = (status: string | number | null | undefined) => {
+  const statusMap: Record<number, string> = {
+    0: '待提交',
+    1: '生效',
+    2: '作废'
+  };
+  return statusMap[Number(status)] || '未知';
+};
+
+/** 获取状态标签类型 */
+const getStatusTagType = (status: string | number | null | undefined): 'info' | 'success' | 'danger' | 'warning' | 'primary' => {
+  const typeMap: Record<number, 'info' | 'success' | 'danger'> = {
+    0: 'info',
+    1: 'success',
+    2: 'danger'
+  };
+  return typeMap[Number(status)] || 'info';
+};
+
+/** 提交资质(待提交→生效) */
+const handleSubmitQualification = (row: QualificationVO) => {
+  ElMessageBox.confirm('确定要提交该资质吗?提交后将无法编辑。', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      try {
+        await submitQualification(row.id);
+        ElMessage.success('提交成功');
+        getQualificationList();
+      } catch (e) {
+        console.error('提交失败:', e);
+        ElMessage.error('提交失败');
+      }
+    })
+    .catch(() => {});
+};
+
+/** 作废资质(生效→作废) */
+const handleVoidQualification = (row: QualificationVO) => {
+  ElMessageBox.confirm('确定要作废该资质吗?作废后将无法恢复。', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      try {
+        await voidQualification(row.id);
+        ElMessage.success('作废成功');
+        getQualificationList();
+      } catch (e) {
+        console.error('作废失败:', e);
+        ElMessage.error('作废失败');
+      }
+    })
+    .catch(() => {});
+};
 </script>
 
 <style scoped>

+ 100 - 31
src/views/supplier/contractsupply/components/Product.vue

@@ -34,7 +34,6 @@
                 <el-select v-model="queryParams.productStatus" placeholder="请选择" clearable style="width: 100%">
                   <el-option label="已上架" :value="1" />
                   <el-option label="下架" :value="0" />
-                  <el-option label="上架中" :value="2" />
                 </el-select>
               </el-form-item>
             </el-col>
@@ -68,7 +67,11 @@
             </template>
           </el-table-column>
           <el-table-column label="产品名称" align="center" prop="itemName" min-width="180" show-overflow-tooltip />
-          <el-table-column label="产品类型" align="center" prop="categoryName" width="110" />
+          <el-table-column label="产品分类" align="center" width="180" show-overflow-tooltip>
+            <template #default="scope">
+              <span>{{ scope.row.topCategoryName }}-{{ scope.row.mediumCategoryName }}-{{ scope.row.bottomCategoryName }}</span>
+            </template>
+          </el-table-column>
           <el-table-column label="品牌" align="center" prop="brandName" width="100" />
           <el-table-column label="单位" align="center" prop="unitName" width="70" />
           <el-table-column label="市场价" align="center" width="90">
@@ -76,7 +79,7 @@
               <span>¥{{ scope.row.marketPrice || '0.00' }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="平台售价" align="center" width="90">
+          <el-table-column label="官网价" align="center" width="90">
             <template #default="scope">
               <span>¥{{ scope.row.memberPrice || '0.00' }}</span>
             </template>
@@ -86,10 +89,14 @@
             <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="130">
+            <template #default="scope">
+              <el-input v-model="scope.row._offerPrice" placeholder="请输入供应价" size="small" />
+            </template>
+          </el-table-column>
           <el-table-column label="操作" align="center" width="100" fixed="right"> <!-- 固定右侧,避免空白 -->
             <template #default="scope">
               <el-button link type="primary" @click="handleAddToList(scope.row)">加入清单</el-button>
@@ -150,9 +157,13 @@ const handleClose = () => {
 
 // 模拟接口(如果实际项目有,可替换)
 import { listBase, categoryTree } from '@/api/product/base';
+import { addContracproduct } from '@/api/product/contracproduct';
 import { BaseVO, BaseQuery, BaseForm } from '@/api/product/base/types';
 import { categoryTreeVO } from '@/api/product/category/types';
 import Pagination from '@/components/Pagination/index.vue';
+import { useUserStore } from '@/store/modules/user';
+
+const userStore = useUserStore();
 
 // 类型补充
 interface PageData<F, Q> {
@@ -326,36 +337,63 @@ const handleSelectionChange = (selection: BaseVO[]) => {
 };
 
 /** 加入清单 */
-const handleAddToList = (row: BaseVO) => {
+const handleAddToList = async (row: BaseVO) => {
   const existsInModelValue = props.modelValue.some(item => item.productId === row.id);
   if (existsInModelValue) {
     proxy?.$modal.msgWarning('该商品已在清单中');
     return;
   }
 
-  const newProduct = {
-    productId: row.id,
-    productCode: row.productNo,
-    productImage: row.productImage,
-    productName: row.itemName,
-    productType: row.categoryName,
-    brand: row.brandName,
-    unit: row.unitName,
-    basePrice: row.purchasingPrice || 0,
-    marketPrice: row.marketPrice || 0,
-    platformPrice: row.memberPrice || 0,
-    offerPrice: '',
-    supplyCycle: '',
-    upPrice: Number(row.productStatus) === 1 ? '上架' : '下架'
-  };
+  const offerPrice = (row as any)._offerPrice || '';
+  if (!offerPrice) {
+    proxy?.$modal.msgWarning('请输入供应价');
+    return;
+  }
 
-  emit('update:modelValue', [...props.modelValue, newProduct]);
-  proxy?.$modal.msgSuccess('添加成功');
-  emit('update:visible', false);
+  // 校验供应价 ≤ 官网价
+  const memberPrice = parseFloat(String(row.memberPrice || 0));
+  const offerPriceNum = parseFloat(String(offerPrice));
+  if (!isNaN(offerPriceNum) && !isNaN(memberPrice) && memberPrice > 0) {
+    if (offerPriceNum > memberPrice) {
+      proxy?.$modal.msgWarning('供应价不能大于官网价');
+      return;
+    }
+  }
+
+  try {
+    await addContracproduct({
+      productId: row.id,
+      productNo: row.productNo,
+      offerPrice: offerPrice,
+      supplierId: userStore.supplierId
+    } as any);
+
+    const newProduct = {
+      productId: row.id,
+      productCode: row.productNo,
+      productImage: row.productImage,
+      productName: row.itemName,
+      productType: row.categoryName,
+      brand: row.brandName,
+      unit: row.unitName,
+      basePrice: row.purchasingPrice || 0,
+      marketPrice: row.marketPrice || 0,
+      platformPrice: row.memberPrice || 0,
+      supplyPrice: offerPrice,
+      supplyCycle: '',
+      upPrice: Number(row.productStatus) === 1 ? '上架' : '下架'
+    };
+
+    emit('update:modelValue', [...props.modelValue, newProduct]);
+    proxy?.$modal.msgSuccess('添加成功');
+  } catch (e) {
+    console.error('添加商品报价失败:', e);
+    proxy?.$modal.msgError('添加失败');
+  }
 };
 
 /** 批量加入清单 */
-const handleBatchAddToList = () => {
+const handleBatchAddToList = async () => {
   const selectedProducts = baseList.value.filter(item => ids.value.includes(item.id));
 
   if (selectedProducts.length === 0) {
@@ -363,11 +401,40 @@ const handleBatchAddToList = () => {
     return;
   }
 
+  // 检查所有选中商品是否都填了供应价
+  const noPrice = selectedProducts.find(row => !(row as any)._offerPrice);
+  if (noPrice) {
+    proxy?.$modal.msgWarning('请为所有选中商品填写供应价');
+    return;
+  }
+
+  // 校验供应价 ≤ 官网价
+  for (const row of selectedProducts) {
+    const memberPrice = parseFloat(String(row.memberPrice || 0));
+    const offerPriceNum = parseFloat(String((row as any)._offerPrice || ''));
+    if (!isNaN(offerPriceNum) && !isNaN(memberPrice) && memberPrice > 0) {
+      if (offerPriceNum > memberPrice) {
+        proxy?.$modal.msgWarning(`商品「${row.itemName}」供应价不能大于官网价`);
+        return;
+      }
+    }
+  }
+
   const newProducts: any[] = [];
+  let successCount = 0;
 
-  selectedProducts.forEach(row => {
+  for (const row of selectedProducts) {
     const exists = props.modelValue.some(item => item.productId === row.id);
-    if (!exists) {
+    if (exists) continue;
+
+    try {
+      await addContracproduct({
+        productId: row.id,
+        productNo: row.productNo,
+        offerPrice: (row as any)._offerPrice,
+        supplierId: userStore.supplierId
+      } as any);
+
       newProducts.push({
         productId: row.id,
         productCode: row.productNo,
@@ -379,21 +446,23 @@ const handleBatchAddToList = () => {
         basePrice: row.purchasingPrice || 0,
         marketPrice: row.marketPrice || 0,
         platformPrice: row.memberPrice || 0,
-        offerPrice: '',
+        supplyPrice: (row as any)._offerPrice,
         supplyCycle: '',
         upPrice: Number(row.productStatus) === 1 ? '上架' : '下架'
       });
+      successCount++;
+    } catch (e) {
+      console.error('添加商品报价失败:', e);
     }
-  });
+  }
 
   if (newProducts.length === 0) {
-    proxy?.$modal.msgWarning('所选商品已在清单中');
+    proxy?.$modal.msgWarning('所选商品已在清单中或添加失败');
     return;
   }
 
   emit('update:modelValue', [...props.modelValue, ...newProducts]);
-  proxy?.$modal.msgSuccess(`成功添加${newProducts.length}个商品`);
-  emit('update:visible', false);
+  proxy?.$modal.msgSuccess(`成功添加${successCount}个商品`);
 };
 
 /** 查询分类树 */

+ 196 - 256
src/views/supplier/contractsupply/edit.vue

@@ -1,149 +1,64 @@
 <template>
   <div class="app-container">
-    <!-- 页面头部 -->
-    <div class="detail-header">
-      <el-icon class="back-icon" @click="goBack"><ArrowLeft /></el-icon>
-      <span class="header-title">编辑合约供货</span>
-    </div>
 
-    <!-- 表单卡片 -->
-    <el-card shadow="never" class="form-card">
-      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px" class="large-form">
-        <el-row :gutter="20">
-          <el-col :span="8">
-            <el-form-item label="协议编号" prop="contractSupplyNo">
-              <span class="form-text">{{ form.contractSupplyNo }}</span>
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="所属公司" prop="companyId">
-              <el-select v-model="form.companyId" placeholder="请选择" style="width: 100%;" size="large">
-                <el-option
-                  v-for="item in companyList"
-                  :key="item.id"
-                  :label="item.companyName"
-                  :value="item.id"
-                />
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <el-row :gutter="20">
-          <el-col :span="8">
-            <el-form-item label="开始时间" prop="startTime">
-              <el-date-picker
-                v-model="form.startTime"
-                type="date"
-                placeholder="请选择"
-                value-format="YYYY-MM-DD"
-                style="width: 100%;"
-                size="large"
-              />
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="结束时间" prop="endTime">
-              <el-date-picker
-                v-model="form.endTime"
-                type="date"
-                placeholder="请选择"
-                value-format="YYYY-MM-DD"
-                style="width: 100%;"
-                size="large"
-              />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <el-row :gutter="20">
-          <el-col :span="24">
-            <el-form-item label="附件" prop="attachment">
-              <FileUpload
-                v-model="form.attachment"
-                :limit="10"
-                :file-size="50"
-                :file-type="['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'pdf']"
-              />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <el-row :gutter="20">
-          <el-col :span="24">
-            <el-form-item label="备注" prop="remark">
-              <el-input
-                v-model="form.remark"
-                type="textarea"
-                :rows="4"
-                placeholder="请输入内容"
-                size="large"
-              />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <!-- 商品列表 -->
-        <div class="section-title">
-          <el-button type="primary" @click="handleAddProduct">添加商品报价</el-button>
-        </div>
 
-        <el-table :data="productList" border style="width: 100%; margin-top: 20px;">
-          <el-table-column prop="productCode" label="产品编号" align="center" width="120" />
-          <el-table-column label="产品图片" align="center" width="100">
-            <template #default="scope">
-              <el-image
-                v-if="scope.row.productImage"
-                :src="scope.row.productImage"
-                style="width: 60px; height: 60px;"
-                fit="cover"
-              />
-            </template>
-          </el-table-column>
-          <el-table-column prop="productName" label="产品名称" align="center" show-overflow-tooltip />
-          <el-table-column prop="productType" label="产品类型" align="center" width="120" />
-          <el-table-column prop="brand" label="品牌" align="center" width="100" />
-          <el-table-column prop="unit" label="单位" align="center" width="80" />
-          <el-table-column label="市场价" align="center" width="100">
-            <template #default="scope">
-              <span>¥{{ scope.row.marketPrice || '0.00' }}</span>
-            </template>
-          </el-table-column>
-          <el-table-column label="平台价" align="center" width="100">
-            <template #default="scope">
-              <span>¥{{ scope.row.platformPrice || '0.00' }}</span>
-            </template>
-          </el-table-column>
-          <el-table-column label="供应价(元)" align="center" width="120">
-            <template #default="scope">
-              <el-input v-model="scope.row.supplyPrice" placeholder="请输入" />
-            </template>
-          </el-table-column>
-          <el-table-column label="供应时效" align="center" width="120">
-            <template #default="scope">
-              <el-input v-model="scope.row.supplyCycle" placeholder="请输入" />
-            </template>
-          </el-table-column>
-          <el-table-column label="上架状态" align="center" width="100">
-            <template #default="scope">
-              <span>{{ scope.row.upPrice }}</span>
-            </template>
-          </el-table-column>
-          <el-table-column label="操作" align="center" width="100" fixed="right">
-            <template #default="scope">
-              <el-button link type="primary" @click="handleDeleteProduct(scope.$index)">删除</el-button>
-            </template>
-          </el-table-column>
-        </el-table>
-
-
-
-        <el-row style="margin-top: 20px;">
-          <el-col :span="24" style="text-align: center;">
-            <el-button type="primary" @click="handleSubmit">提交</el-button>
-          </el-col>
-        </el-row>
-      </el-form>
+    <!-- 商品报价信息 -->
+    <el-card shadow="never" class="form-card">
+      <div class="section-title">
+        <el-button type="primary" @click="handleAddProduct">添加商品报价</el-button>
+        <el-button type="success" icon="Upload" @click="handleImport">导入</el-button>
+        <el-button icon="Download" @click="handleDownloadTemplate">下载模板</el-button>
+      </div>
+
+      <el-table :data="productList" border style="width: 100%; margin-top: 20px;">
+        <el-table-column prop="productNo" label="产品编号" align="center" width="120" />
+        <el-table-column label="产品图片" align="center" width="100">
+          <template #default="scope">
+            <el-image
+              v-if="scope.row.productImage"
+              :src="scope.row.productImage"
+              style="width: 60px; height: 60px;"
+              fit="cover"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column prop="itemName" label="产品名称" align="center" show-overflow-tooltip />
+        <el-table-column label="产品分类" align="center" width="180" show-overflow-tooltip>
+          <template #default="scope"> 
+            <span>{{ scope.row.topCategoryName }}-{{ scope.row.mediumCategoryName }}-{{ scope.row.bottomCategoryName }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="brandName" label="品牌" align="center" width="100" />
+        <el-table-column prop="unitName" label="单位" align="center" width="80" />
+        <el-table-column label="市场价" align="center" width="100">
+          <template #default="scope">
+            <span>¥{{ scope.row.marketPrice || '0.00' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="官网价" align="center" width="100">
+          <template #default="scope">
+            <span>¥{{ scope.row.memberPrice || '0.00' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="供应价(元)" align="center" width="120">
+          <template #default="scope">
+            <span>¥{{ scope.row.supplyPrice || '0.00' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="100" fixed="right">
+          <template #default="scope">
+            <el-button link type="primary" @click="handleDeleteProduct(scope.row, scope.$index)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination
+        v-show="total > 0"
+        :total="total"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getProductData"
+      />
     </el-card>
 
     <!-- 添加商品对话框 -->
@@ -151,155 +66,188 @@
       v-model:visible="productDialog.visible"
       v-model:modelValue="productList"
     />
+
+    <!-- 导入对话框 -->
+    <el-dialog v-model="upload.open" title="合同产品导入" width="400px" append-to-body>
+      <el-upload
+        ref="uploadRef"
+        :limit="1"
+        accept=".xlsx, .xls"
+        :auto-upload="false"
+        :http-request="customUpload"
+        :on-change="handleFileChange"
+        :on-progress="handleFileUploadProgress"
+        :on-success="handleFileSuccess"
+        :on-error="handleFileError"
+        drag
+      >
+        <el-icon class="el-icon--upload">
+          <i-ep-upload-filled />
+        </el-icon>
+        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+        <template #tip>
+          <div class="text-center el-upload__tip">
+            <span>仅允许导入 xls、xlsx 格式文件。</span>
+            <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="handleDownloadTemplate">下载模板</el-link>
+          </div>
+        </template>
+      </el-upload>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" :loading="upload.isUploading" @click="submitFileForm">确 定</el-button>
+          <el-button @click="upload.open = false">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted, reactive } from 'vue';
-import { useRoute, useRouter } from 'vue-router';
-import { ArrowLeft } from '@element-plus/icons-vue';
-import { getContractsupply, updateContractsupply } from '@/api/supplier/contractsupply';
-import { getCompanyList } from '@/api/customer/info';
-import { ElMessage } from 'element-plus';
-import FileUpload from '@/components/FileUpload/index.vue';
+import { ref, onMounted, reactive, getCurrentInstance } from 'vue';
+import { useRouter } from 'vue-router';
+import { getContractProductList, delContracproductBySupplier, importTemplate, importData } from '@/api/product/contracproduct';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import type { UploadFile, UploadInstance, UploadRequestOptions } from 'element-plus';
 import Product from './components/Product.vue';
 import { useUserStore } from '@/store/modules/user';
 
-const route = useRoute();
+const { proxy } = getCurrentInstance() as any;
 const router = useRouter();
 const userStore = useUserStore();
 
-const formRef = ref();
 const productList = ref<any[]>([]);
-const companyList = ref<any[]>([]);
+const total = ref(0);
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10
+});
 
 // 商品对话框相关
 const productDialog = reactive({
   visible: false
 });
 
-const form = ref({
-  id: '',
-  contractSupplyNo: '',
-  companyId: '',
-  supplierId: '',
-  supplyNo: '',
-  supplierName: '',
-  startTime: '',
-  endTime: '',
-  attachment: '',
-  remark: '',
-  isSubmit: false
+// 导入相关
+const uploadRef = ref<UploadInstance>();
+const upload = reactive({
+  open: false,
+  isUploading: false
 });
 
-const rules = {
-  // 编辑页面所有字段都不是必填
-};
-
 /** 返回上一页 */
 const goBack = () => {
   router.back();
 };
 
-/** 获取公司列表 */
-const getCompanyData = async () => {
+/** 获取商品列表 */
+const getProductData = async () => {
   try {
-    const res = await getCompanyList({ pageNum: 1, pageSize: 1000 });
-    companyList.value = res.rows || res.data || [];
+    const res = await getContractProductList({
+      supplierId: userStore.supplierId,
+      pageNum: queryParams.pageNum,
+      pageSize: queryParams.pageSize
+    } as any);
+    productList.value = res.rows || []
+    total.value = (res as any).total || 0;
   } catch (e) {
-    console.error('获取公司列表失败:', e);
+    console.error('获取商品列表失败:', e);
+    ElMessage.error('获取商品列表失败');
   }
 };
 
-/** 获取详情数据 */
-const getDetail = async () => {
-  const id = route.params.id as string;
-  if (id) {
-    try {
-      const res = await getContractsupply(id);
-      const data: any = res.data;
+/** 添加商品 */
+const handleAddProduct = () => {
+  productDialog.visible = true;
+};
 
-      // 回显基本信息
-      form.value = {
-        id: String(data.id ?? ''),
-        contractSupplyNo: String(data.contractSupplyNo ?? ''),
-        companyId: data.companyId ?? '',
-        supplierId: (userStore.supplierId ?? data.supplierId) as any,
-        supplyNo: data.supplyNo || '',
-        supplierName: data.supplierName,
-        startTime: data.startTime,
-        endTime: data.endTime,
-        attachment: data.attachment,
-        remark: data.remark,
-        isSubmit: data.isSubmit || false
-      };
+/** 删除商品 */
+const handleDeleteProduct = async (row: any, index: number) => {
+  console.log('handleDeleteProduct', row, index);
+  try {
+    await proxy?.$modal.confirm('是否确认删除该商品报价数据项?');
+    await delContracproductBySupplier(row.id, userStore.supplierId as any);
+    proxy?.$modal.msgSuccess('删除成功');
+    await getProductData();
+  } catch (e) {
+    // 用户取消或请求失败时不提示
+  }
+};
+
+/** 打开导入弹窗 */
+const handleImport = () => {
+  uploadRef.value?.clearFiles();
+  upload.open = true;
+};
 
-      // 回显商品列表(关键!需要字段映射)
-      productList.value = (data.contractProduct || []).map((item: any) => ({
-        productId: item.productId,
-        productCode: item.productNo,
-        productImage: item.productImage,
-        productName: item.itemName,
-        productType: item.categoryName,
-        brand: item.brandName,
-        unit: item.unitName,
-        basePrice: item.purchasingPrice || 0,
-        marketPrice: item.marketPrice || 0,
-        platformPrice: item.memberPrice || 0,
-        supplyPrice: item.offerPrice || '',
-        supplyCycle: item.supplyCycle || '',
-        upPrice: item.productStatus === 1 ? '上架' : '下架'
-      }));
-    } catch (e) {
-      console.error('获取详情失败:', e);
-      ElMessage.error('获取详情失败');
-    }
+/** 下载导入模板 */
+const handleDownloadTemplate = async () => {
+  try {
+    const data: any = await importTemplate();
+    const blob = new Blob([data], {
+      type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+    });
+    const link = document.createElement('a');
+    link.href = window.URL.createObjectURL(blob);
+    link.download = `contracproduct_template_${new Date().getTime()}.xlsx`;
+    document.body.appendChild(link);
+    link.click();
+    document.body.removeChild(link);
+    window.URL.revokeObjectURL(link.href);
+  } catch (e) {
+    console.error('下载模板失败:', e);
+    ElMessage.error('下载模板失败');
   }
 };
 
-/** 添加商品 */
-const handleAddProduct = () => {
-  productDialog.visible = true;
+/** 文件选择变更 */
+const handleFileChange = () => {
+  // 占位:用于触发响应(element-plus 类型需要)
 };
 
-/** 删除商品 */
-const handleDeleteProduct = (index: number) => {
-  productList.value.splice(index, 1);
+/** 自定义上传:直接调用后端 importData 接口 */
+const customUpload = async (options: UploadRequestOptions) => {
+  upload.isUploading = true;
+  try {
+    const res: any = await importData(options.file as File);
+    options.onSuccess?.(res);
+  } catch (err: any) {
+    options.onError?.(err);
+  }
 };
 
-/** 提交表单 */
-const handleSubmit = async () => {
-  formRef.value?.validate(async (valid: boolean) => {
-    if (valid) {
-      try {
-        // 组装提交数据,包含表单数据和商品列表
-        const submitData = {
-          ...form.value,
-          contractProduct: productList.value.map(item => ({
-            productId: item.productId,
-            productNo: item.productCode,
-            offerPrice: item.supplyPrice || '',
-            supplyCycle: item.supplyCycle || ''
-          }))
-        };
+/** 文件上传中处理 */
+const handleFileUploadProgress = () => {
+  upload.isUploading = true;
+};
 
-        console.log('提交的数据:', submitData);
-        console.log('商品列表:', productList.value);
+/** 文件上传成功处理 */
+const handleFileSuccess = (response: any, file: UploadFile) => {
+  upload.open = false;
+  upload.isUploading = false;
+  uploadRef.value?.handleRemove(file);
+  const msg = response?.msg || '导入成功';
+  ElMessageBox.alert(
+    "<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + msg + '</div>',
+    '导入结果',
+    { dangerouslyUseHTMLString: true }
+  );
+  queryParams.pageNum = 1;
+  getProductData();
+};
+
+/** 文件上传失败处理 */
+const handleFileError = () => {
+  upload.isUploading = false;
+  ElMessage.error('导入失败');
+};
 
-        await updateContractsupply(submitData);
-        ElMessage.success('保存成功');
-        router.back();
-      } catch (e) {
-        console.error('保存失败:', e);
-        ElMessage.error('保存失败');
-      }
-    }
-  });
+/** 提交导入 */
+const submitFileForm = () => {
+  uploadRef.value?.submit();
 };
 
 onMounted(() => {
-  getDetail();
-  getCompanyData();
+  getProductData();
 });
 </script>
 
@@ -340,14 +288,6 @@ onMounted(() => {
   padding: 24px;
 }
 
-.large-form :deep(.el-form-item__label) {
-  font-weight: 500;
-}
-
-.form-text {
-  color: #303133;
-}
-
 .section-title {
   margin: 20px 0;
   padding-bottom: 12px;

+ 0 - 1
src/views/supplier/contractsupply/index.vue

@@ -27,7 +27,6 @@
             <el-form-item>
               <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
               <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-              <el-button type="primary" icon="Plus" @click="handleAdd" >新增</el-button>
             </el-form-item>