Bläddra i källkod

feat(supplier): 新增合约供货管理功能

- 新增合约供货添加页面,支持供应商选择、时间设置和商品信息管理
- 新增合约供货编辑页面,支持协议信息修改和商品报价调整
- 新增合约供货列表页面,支持协议查询、审核和编辑操作
- 更新产品价格校验逻辑,将供应价<官网价<市场价关系验证添加到产品管理中
- 优化税码选择组件,对非叶子节点添加禁用样式和选择限制
- 移除基础产品列表中的重复操作列定义
- 修复供应商资质新增时供应商ID自动填充问题
肖路 2 dagar sedan
förälder
incheckning
7805a26397

+ 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>

+ 1 - 0
src/views/login.vue

@@ -149,6 +149,7 @@ const handleLogin = () => {
         localStorage.removeItem('password');
         localStorage.removeItem('rememberMe');
       }
+      loginForm.value.userSonType = '1';
       // 调用action的登录方法
       const [err] = await to(userStore.login(loginForm.value));
       if (!err) {

+ 22 - 14
src/views/product/base/add.vue

@@ -412,7 +412,7 @@
               </el-col>
               <el-col :span="8">
                 <el-form-item label="备注:">
-                  <span class="currency-text">市场价>官网价</span>
+                  <span class="currency-text">供应价 &lt; 官网价 &lt; 市场价</span>
                 </el-form-item>
               </el-col>
             </el-row>
@@ -1268,18 +1268,30 @@ const handleSubmit = async () => {
       return;
     }
 
-    // 校验价格关系:市场价 > 官网价 > 最低售价
-    const midRange = parseFloat(String(productForm.marketPrice));
-    const standard = parseFloat(String(productForm.memberPrice));
-    const certificate = parseFloat(String(productForm.minSellingPrice));
-    if (!isNaN(midRange) && !isNaN(standard) && !isNaN(certificate)) {
-      if (!(midRange > standard)) {
-        ElMessage.warning('市场价必须大于官网价');
+    // 校验价格关系:供应价 < 官网价 < 市场价(任一字段为空时跳过对应校验)
+    const marketPriceNum = parseFloat(String(productForm.marketPrice));
+    const memberPriceNum = parseFloat(String(productForm.memberPrice));
+    const supplyPriceNum = parseFloat(String(productForm.supplyPrice));
+    // 官网价 < 市场价
+    if (!isNaN(memberPriceNum) && !isNaN(marketPriceNum)) {
+      if (!(memberPriceNum < marketPriceNum)) {
+        ElMessage.warning('官网价必须小于市场价');
         submitLoading.value = false;
         return;
       }
-      if (!(standard > certificate)) {
-        ElMessage.warning('官网价必须大于最低售价');
+    }
+    // 供应价 < 官网价
+    if (!isNaN(supplyPriceNum) && !isNaN(memberPriceNum)) {
+      if (!(supplyPriceNum < memberPriceNum)) {
+        ElMessage.warning('供应价必须小于官网价');
+        submitLoading.value = false;
+        return;
+      }
+    }
+    // 供应价 < 市场价(当官网价为空但供应价与市场价都存在时也校验)
+    if (!isNaN(supplyPriceNum) && !isNaN(marketPriceNum)) {
+      if (!(supplyPriceNum < marketPriceNum)) {
+        ElMessage.warning('供应价必须小于市场价');
         submitLoading.value = false;
         return;
       }
@@ -1471,10 +1483,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);
   }

+ 0 - 40
src/views/product/base/index.vue

@@ -227,46 +227,6 @@
 <!--            <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>-->
-
-<!--            &lt;!&ndash; 审核通过 &ndash;&gt;-->
-<!--            <div v-else-if="scope.row.productReviewStatus === 1" class="flex flex-col gap-1">-->
-<!--              &lt;!&ndash; 下架状态:编辑、上架、停售、修改库存 &ndash;&gt;-->
-<!--              <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>-->
-
-<!--              &lt;!&ndash; 上架状态:编辑、下架、停售、修改库存 &ndash;&gt;-->
-<!--              <div v-else-if="scope.row.productStatus === 1" class="flex gap-1 justify-center">-->
-<!--                <el-link type="primary" :underline="false" @click="handleUpdate(scope.row)">编辑</el-link>-->
-<!--                <el-link type="warning" :underline="false" @click="handleShelf(scope.row)">下架</el-link>-->
-<!--                <el-link type="danger" :underline="false" @click="handleDiscontinue(scope.row)">停售</el-link>-->
-<!--              </div>-->
-<!--              <div v-if="scope.row.productStatus === 1 && scope.row.isSelf === 0" class="flex gap-1 justify-center">-->
-<!--                <el-link type="info" :underline="false" @click="handleAddToSelfPool(scope.row)">加入自营池</el-link>-->
-<!--              </div>-->
-<!--              &lt;!&ndash; 自营商品显示加入精品池按钮 &ndash;&gt;-->
-<!--              <div v-if="scope.row.productStatus === 1 && scope.row.isSelf === 1 && scope.row.productCategory === 1" class="flex gap-1 justify-center">-->
-<!--                <el-link type="warning" :underline="false" @click="handleAddToExquisitePool(scope.row)">加入精品池</el-link>-->
-<!--              </div>-->
-<!--            </div>-->
-
-            <!-- 其他状态(待提交、审核驳回等):显示编辑 -->
-            <div 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>
 
       <!-- 游标分页控制 -->

+ 19 - 14
src/views/product/baseAudit/add.vue

@@ -393,7 +393,7 @@
               </el-col>
               <el-col :span="8">
                 <el-form-item label="备注:">
-                  <span class="currency-text">市场价>官网价</span>
+                  <span class="currency-text">供应价 &lt; 官网价 &lt; 市场价</span>
                 </el-form-item>
               </el-col>
             </el-row>
@@ -1248,18 +1248,27 @@ const handleSubmit = async () => {
       return;
     }
 
-    // 校验价格关系:市场价 > 官网价 > 最低售
-    const midRange = parseFloat(String(productForm.marketPrice));
-    const standard = parseFloat(String(productForm.memberPrice));
-    const certificate = parseFloat(String(productForm.minSellingPrice));
-    if (!isNaN(midRange) && !isNaN(standard) && !isNaN(certificate)) {
-      if (!(midRange > standard)) {
-        ElMessage.warning('市场价必须大于官网价');
+    // 校验价格关系:供应价 < 官网价 < 市场
+    const marketPriceNum = parseFloat(String(productForm.marketPrice));
+    const memberPriceNum = parseFloat(String(productForm.memberPrice));
+    const supplyPriceNum = parseFloat(String(productForm.supplyPrice));
+    if (!isNaN(marketPriceNum) && !isNaN(memberPriceNum)) {
+      if (!(memberPriceNum < marketPriceNum)) {
+        ElMessage.warning('官网价必须小于市场价');
         submitLoading.value = false;
         return;
       }
-      if (!(standard > certificate)) {
-        ElMessage.warning('官网价必须大于最低售价');
+    }
+    if (!isNaN(supplyPriceNum) && !isNaN(memberPriceNum)) {
+      if (!(supplyPriceNum < memberPriceNum)) {
+        ElMessage.warning('供应价必须小于官网价');
+        submitLoading.value = false;
+        return;
+      }
+    }
+    if (!isNaN(supplyPriceNum) && !isNaN(marketPriceNum)) {
+      if (!(supplyPriceNum < marketPriceNum)) {
+        ElMessage.warning('供应价必须小于市场价');
         submitLoading.value = false;
         return;
       }
@@ -1452,10 +1461,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);
   }

+ 1 - 1
src/views/supplier/aptitude/index.vue

@@ -582,7 +582,7 @@ const handleSubmit = async () => {
       }
     } else {
       // 新增
-
+      formData.value.supplierId = userStore.supplierId;
       await addQualification(formData.value);
 
       ElMessage.success('新增成功');

+ 356 - 0
src/views/supplier/contractsupply/add.vue

@@ -0,0 +1,356 @@
+<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">
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="所属公司" prop="companyId">
+              <el-select v-model="form.companyId" placeholder="请选择" style="width: 100%;">
+                <el-option
+                  v-for="item in companyList"
+                  :key="item.id"
+                  :label="item.companyName"
+                  :value="item.id"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="供应商" prop="supplierId">
+              <el-select v-model="form.supplierId" placeholder="请选择" style="width: 100%;" @change="handleSupplierChange">
+                <el-option
+                  v-for="item in supplierList"
+                  :key="item.id"
+                  :label="item.enterpriseName"
+                  :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%;"
+              />
+            </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%;"
+              />
+            </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.proFile"
+                :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="请输入内容"
+              />
+            </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.offerPrice"
+                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>
+
+    <!-- 添加商品对话框 -->
+    <Product
+      v-model:visible="productDialog.visible"
+      v-model:modelValue="productList"
+      @confirm="handleProductConfirm"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted, reactive } from 'vue';
+import { useRouter } from 'vue-router';
+import { ArrowLeft } from '@element-plus/icons-vue';
+import { addContractsupply } from '@/api/supplier/contractsupply';
+import { getCompanyList, listInfo } from '@/api/customer/info';
+import { ElMessage } from 'element-plus';
+import FileUpload from '@/components/FileUpload/index.vue';
+import Product from './components/Product.vue';
+import type { BaseVO } from '@/api/product/base/types';
+
+const router = useRouter();
+
+const formRef = ref();
+const productList = ref<any[]>([]);
+const companyList = ref<any[]>([]);
+const supplierList = ref<any[]>([]);
+
+// 商品对话框相关
+const productDialog = reactive({
+  visible: false
+});
+
+const form = ref({
+  companyId: '',
+  supplierId: '',
+  supplyNo: '',
+  startTime: '',
+  endTime: '',
+  attachment: '',
+  proFile: '',
+  remark: '',
+  isSubmit: false
+});
+
+const rules = {
+  supplierId: [
+    { required: true, message: '请选择供应商', trigger: 'change' }
+  ]
+};
+
+/** 返回上一页 */
+const goBack = () => {
+  router.back();
+};
+
+/** 获取公司列表 */
+const getCompanyData = async () => {
+  try {
+    const res = await getCompanyList({ pageNum: 1, pageSize: 1000 });
+    companyList.value = res.rows || res.data || [];
+  } catch (e) {
+    console.error('获取公司列表失败:', e);
+  }
+};
+
+/** 获取供应商列表 */
+const getSupplierData = async () => {
+  try {
+    const res = await listInfo();
+    supplierList.value = res.rows || [];
+  } catch (e) {
+    console.error('获取供应商列表失败:', e);
+  }
+};
+
+/** 供应商选择变化 */
+const handleSupplierChange = (value: any) => {
+  const supplier = supplierList.value.find(item => String(item.id) === String(value));
+  if (supplier) {
+    form.value.supplyNo = supplier.supplierNo || '';
+  }
+};
+
+/** 添加商品 */
+const handleAddProduct = () => {
+  productDialog.visible = true;
+};
+
+/** 处理商品选择确认 */
+const handleProductConfirm = (selectedProducts: BaseVO[]) => {
+  if (selectedProducts.length === 0) {
+    ElMessage.warning('请选择要添加的商品');
+    return;
+  }
+
+  productList.value = selectedProducts;
+  ElMessage.success(`成功添加${selectedProducts.length}个商品`);
+};
+
+/** 删除商品 */
+const handleDeleteProduct = (index: number) => {
+  productList.value.splice(index, 1);
+};
+
+/** 提交表单 */
+const handleSubmit = async () => {
+  formRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      try {
+        // 从表格中收集最新的数据
+        const tableInputs = document.querySelectorAll('.el-table input');
+        let inputIndex = 0;
+
+        productList.value.forEach((item, index) => {
+          // 每行有两个输入框:供应价和供应时效
+          if (tableInputs[inputIndex]) {
+            item.offerPrice = (tableInputs[inputIndex] as HTMLInputElement).value;
+            inputIndex++;
+          }
+          if (tableInputs[inputIndex]) {
+            item.supplyCycle = (tableInputs[inputIndex] as HTMLInputElement).value;
+            inputIndex++;
+          }
+        });
+
+        // 组装提交数据,包含表单数据和商品列表
+        const submitData = {
+          ...form.value,
+          contractProduct: productList.value.map(item => ({
+            productId: item.productId,
+            productNo: item.productCode,
+            offerPrice: item.offerPrice || '',
+            supplyCycle: item.supplyCycle || ''
+          }))
+        };
+
+        console.log('提交的数据:', submitData);
+        console.log('商品列表:', productList.value);
+
+        await addContractsupply(submitData);
+        ElMessage.success('新增成功');
+        router.back();
+      } catch (e) {
+        console.error('新增失败:', e);
+        ElMessage.error('新增失败');
+      }
+    }
+  });
+};
+
+onMounted(() => {
+  getCompanyData();
+  getSupplierData();
+});
+</script>
+
+<style scoped>
+.app-container {
+  background: #f0f2f5;
+  min-height: 100vh;
+  padding: 0;
+}
+
+.detail-header {
+  background: #fff;
+  padding: 16px 24px;
+  display: flex;
+  align-items: center;
+  border-bottom: 1px solid #e8e8e8;
+  margin-bottom: 16px;
+}
+
+.back-icon {
+  font-size: 18px;
+  cursor: pointer;
+  margin-right: 12px;
+  color: #666;
+}
+
+.back-icon:hover {
+  color: #409eff;
+}
+
+.header-title {
+  font-weight: 500;
+  color: #333;
+}
+
+.form-card {
+  margin: 16px;
+  padding: 24px;
+}
+
+.section-title {
+  margin: 20px 0;
+  padding-bottom: 12px;
+  border-bottom: 1px solid #e8e8e8;
+}
+</style>

+ 492 - 0
src/views/supplier/contractsupply/components/Product.vue

@@ -0,0 +1,492 @@
+<template>
+  <el-dialog
+    v-model="dialogVisible"
+    title="添加商品报价"
+    width="90%"
+    :close-on-click-modal="false"
+    @close="handleClose"
+    class="product-dialog"
+    :modal-append-to-body="false"
+    :append-to-body="true"
+  >
+    <div class="dialog-content">
+      <!-- 搜索表单区域 - 固定 -->
+      <div class="search-container">
+        <el-form ref="queryFormRef" :model="queryParams" label-width="80px">
+          <el-row :gutter="16" class="first-row">
+            <el-col :span="6"> <!-- 调整为6列,均分24列,避免宽度不足 -->
+              <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="brandName">
+                <el-input v-model="queryParams.brandName" placeholder="请选择" clearable @keyup.enter="handleQuery" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="上架状态:" prop="productStatus">
+                <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>
+          </el-row>
+          <el-row :gutter="16" class="second-row">
+            <el-col :span="24" style="text-align: left;">
+              <el-button type="primary" icon="Plus" @click="handleBatchAddToList">批量加入清单</el-button>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-col>
+          </el-row>
+        </el-form>
+      </div>
+
+      <!-- 表格区域 - 可滚动 -->
+      <div class="table-wrapper">
+        <el-table
+          v-loading="loading"
+          border
+          :fit="true"
+          :data="baseList"
+          @selection-change="handleSelectionChange"
+          max-height="calc(70vh - 200px)"
+          style="width: 100%"
+        >
+          <el-table-column type="selection" width="50" align="center" />
+          <el-table-column label="产品编号" align="center" prop="productNo" width="110" />
+          <el-table-column label="产品图片" align="center" prop="productImage" width="90">
+            <template #default="scope">
+              <image-preview :src="scope.row.productImage" :width="60" :height="60" />
+            </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" prop="brandName" width="100" />
+          <el-table-column label="单位" align="center" prop="unitName" width="70" />
+          <el-table-column label="市场价" align="center" width="90">
+            <template #default="scope">
+              <span>¥{{ scope.row.marketPrice || '0.00' }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="平台售价" align="center" width="90">
+            <template #default="scope">
+              <span>¥{{ scope.row.memberPrice || '0.00' }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="起订量" align="center" prop="minOrderQuantity" width="70" />
+          <el-table-column label="上架状态" align="center" prop="productStatus" width="90">
+            <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="100" fixed="right"> <!-- 固定右侧,避免空白 -->
+            <template #default="scope">
+              <el-button link type="primary" @click="handleAddToList(scope.row)">加入清单</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <!-- 分页 -->
+        <div class="pagination-container">
+          <Pagination
+            v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize"
+            v-model:way="queryParams.way"
+            :cursor-mode="true"
+            :has-more="hasMore"
+            :total="total"
+            @pagination="getList"
+          />
+        </div>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<script setup name="Base" lang="ts">
+// 补充必要的导入
+import { getCurrentInstance, ComponentInternalInstance, computed, ref, reactive, onMounted, toRefs } from 'vue';
+import type { FormInstance } from 'element-plus';
+
+// Props 定义
+interface Props {
+  visible: boolean;
+  modelValue: any[];
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  visible: false,
+  modelValue: () => []
+});
+
+// Emits 定义
+const emit = defineEmits<{
+  'update:visible': [value: boolean];
+  'update:modelValue': [value: any[]];
+  'confirm': [selectedProducts: any[]];
+}>();
+
+// 控制 dialog 显示
+const dialogVisible = computed({
+  get: () => props.visible,
+  set: (val) => emit('update:visible', val)
+});
+
+// 关闭对话框
+const handleClose = () => {
+  emit('update:visible', false);
+};
+
+// 模拟接口(如果实际项目有,可替换)
+import { listBase, categoryTree } from '@/api/product/base';
+import { BaseVO, BaseQuery, BaseForm } from '@/api/product/base/types';
+import { categoryTreeVO } from '@/api/product/category/types';
+import Pagination from '@/components/Pagination/index.vue';
+
+// 类型补充
+interface PageData<F, Q> {
+  form: F;
+  queryParams: Q;
+  rules: Record<string, any>;
+}
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const baseList = ref<BaseVO[]>([]);
+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 hasMore = ref(true);
+const pageHistory = ref([]);
+
+const queryFormRef = ref<FormInstance>();
+
+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,
+    brandName: undefined,
+    productTag: undefined,
+    purchaseNature: undefined,
+    supplierType: undefined,
+    supplierNature: undefined,
+    projectOrg: undefined,
+    topCategoryId: undefined,
+    mediumCategoryId: undefined,
+    bottomCategoryId: undefined,
+    isSelf: undefined,
+    productReviewStatus: undefined,
+    productStatus: undefined,
+    lastSeenId: undefined,
+    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: '产品审核状态不能为空', trigger: 'change' }],
+    homeRecommended: [{ required: true, message: '首页推荐不能为空', trigger: 'blur' }],
+    categoryRecommendation: [{ required: true, message: '分类推荐不能为空', trigger: 'blur' }],
+    cartRecommendation: [{ required: true, message: '购物车推荐不能为空', trigger: 'blur' }],
+    recommendedProductOrder: [{ required: true, message: '推荐产品顺序不能为空', trigger: 'blur' }],
+    isPopular: [{ required: true, message: '是否热门不能为空', trigger: 'blur' }],
+    isNew: [{ required: true, message: '是否新品不能为空', trigger: 'blur' }],
+    remark: [{ required: true, message: '备注不能为空', trigger: 'blur' }]
+  }
+});
+
+const { queryParams } = toRefs(data);
+
+/** 查询产品基础信息列表 */
+const getList = async () => {
+  loading.value = true;
+  try {
+    const params = { ...queryParams.value };
+    const currentPageNum = queryParams.value.pageNum;
+
+    if (currentPageNum === 1) {
+      delete params.lastSeenId;
+      delete params.way;
+    } else {
+      if (queryParams.value.way === 0) {
+        const nextPageHistory = pageHistory.value[currentPageNum];
+        if (nextPageHistory) {
+          params.firstSeenId = nextPageHistory.firstId;
+          params.way = 0;
+        }
+      } else {
+        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;
+
+    if (baseList.value.length > 0) {
+      const firstItem = baseList.value[0];
+      const lastItem = baseList.value[baseList.value.length - 1];
+      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 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.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 handleAddToList = (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 ? '上架' : '下架'
+  };
+
+  emit('update:modelValue', [...props.modelValue, newProduct]);
+  proxy?.$modal.msgSuccess('添加成功');
+  emit('update:visible', false);
+};
+
+/** 批量加入清单 */
+const handleBatchAddToList = () => {
+  const selectedProducts = baseList.value.filter(item => ids.value.includes(item.id));
+
+  if (selectedProducts.length === 0) {
+    proxy?.$modal.msgWarning('请选择要添加的商品');
+    return;
+  }
+
+  const newProducts: any[] = [];
+
+  selectedProducts.forEach(row => {
+    const exists = props.modelValue.some(item => item.productId === row.id);
+    if (!exists) {
+      newProducts.push({
+        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 ? '上架' : '下架'
+      });
+    }
+  });
+
+  if (newProducts.length === 0) {
+    proxy?.$modal.msgWarning('所选商品已在清单中');
+    return;
+  }
+
+  emit('update:modelValue', [...props.modelValue, ...newProducts]);
+  proxy?.$modal.msgSuccess(`成功添加${newProducts.length}个商品`);
+  emit('update:visible', false);
+};
+
+/** 查询分类树 */
+const getCategoryTree = async () => {
+  const res = await categoryTree();
+  categoryOptions.value = res.data || [];
+};
+
+onMounted(() => {
+  getList();
+  getCategoryTree();
+});
+</script>
+
+<style scoped>
+/* 核心:禁止外层滚动,仅表格内部滚动 */
+.product-dialog {
+  overflow: hidden !important;
+}
+
+.product-dialog :deep(.el-dialog__body) {
+  padding: 0;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden !important; /* 禁止对话框主体滚动 */
+  height: calc(90vh); /* 限制对话框主体高度,避免超出视口 */
+}
+
+.dialog-content {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  height: 100%;
+  overflow: hidden !important; /* 禁止内容容器滚动 */
+}
+
+.search-container {
+  flex-shrink: 0;
+  padding: 16px;
+  background: #f5f7fa;
+  border-bottom: 1px solid #e4e7ed;
+  box-sizing: border-box; /* 内边距不影响宽度 */
+  width: 100%;
+}
+
+.search-container :deep(.el-form) {
+  margin-bottom: 0;
+  width: 100%;
+}
+
+.search-container :deep(.el-row) {
+  display: flex;
+  align-items: center;
+  width: 100%;
+}
+
+.search-container .first-row {
+  margin-bottom: 16px;
+}
+
+.search-container :deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+.search-container :deep(.el-form-item__label) {
+  font-weight: normal;
+  white-space: nowrap;
+}
+
+.search-container :deep(.el-input),
+.search-container :deep(.el-select) {
+  width: 100%;
+}
+
+/* 表格容器:仅让表格内部滚动,外层不滚动 */
+.table-wrapper {
+  flex: 1; /* 占满剩余高度 */
+  padding: 16px;
+  box-sizing: border-box;
+  width: 100%;
+  overflow: hidden !important; /* 禁止表格容器滚动 */
+}
+
+.pagination-container {
+  margin-top: 16px;
+  display: flex;
+  justify-content: flex-end;
+  width: 100%;
+  flex-shrink: 0; /* 分页栏不压缩 */
+}
+
+/* 修复固定列样式 */
+.product-dialog :deep(.el-table__fixed-right) {
+  height: calc(100% - 17px) !important; /* 匹配表格高度 */
+}
+</style>

+ 378 - 0
src/views/supplier/contractsupply/edit.vue

@@ -0,0 +1,378 @@
+<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-col :span="8">
+            <el-form-item label="供应商" prop="supplierId">
+              <el-select v-model="form.supplierId" placeholder="请选择" style="width: 100%;" size="large">
+                <el-option
+                  v-for="item in supplierList"
+                  :key="item.id"
+                  :label="item.enterpriseName"
+                  :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>
+
+    <!-- 添加商品对话框 -->
+    <Product
+      v-model:visible="productDialog.visible"
+      v-model:modelValue="productList"
+    />
+  </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, listInfo } from '@/api/customer/info';
+import { ElMessage } from 'element-plus';
+import FileUpload from '@/components/FileUpload/index.vue';
+import Product from './components/Product.vue';
+
+const route = useRoute();
+const router = useRouter();
+
+const formRef = ref();
+const productList = ref<any[]>([]);
+const companyList = ref<any[]>([]);
+const supplierList = ref<any[]>([]);
+
+// 商品对话框相关
+const productDialog = reactive({
+  visible: false
+});
+
+const form = ref({
+  id: '',
+  contractSupplyNo: '',
+  companyId: '',
+  supplierId: '',
+  supplyNo: '',
+  supplierName: '',
+  startTime: '',
+  endTime: '',
+  attachment: '',
+  remark: '',
+  isSubmit: false
+});
+
+const rules = {
+  // 编辑页面所有字段都不是必填
+};
+
+/** 返回上一页 */
+const goBack = () => {
+  router.back();
+};
+
+/** 获取公司列表 */
+const getCompanyData = async () => {
+  try {
+    const res = await getCompanyList({ pageNum: 1, pageSize: 1000 });
+    companyList.value = res.rows || res.data || [];
+  } catch (e) {
+    console.error('获取公司列表失败:', e);
+  }
+};
+
+/** 获取供应商列表 */
+const getSupplierData = async () => {
+  try {
+    const res = await listInfo();
+    supplierList.value = res.rows || [];
+  } catch (e) {
+    console.error('获取供应商列表失败:', e);
+  }
+};
+
+/** 获取详情数据 */
+const getDetail = async () => {
+  const id = route.params.id as string;
+  if (id) {
+    try {
+      const res = await getContractsupply(id);
+      const data: any = res.data;
+
+      // 回显基本信息
+      form.value = {
+        id: String(data.id ?? ''),
+        contractSupplyNo: String(data.contractSupplyNo ?? ''),
+        companyId: data.companyId ?? '',
+        supplierId: data.supplierId ?? '',
+        supplyNo: data.supplyNo || '',
+        supplierName: data.supplierName,
+        startTime: data.startTime,
+        endTime: data.endTime,
+        attachment: data.attachment,
+        remark: data.remark,
+        isSubmit: data.isSubmit || false
+      };
+
+      // 回显商品列表(关键!需要字段映射)
+      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 handleAddProduct = () => {
+  productDialog.visible = true;
+};
+
+/** 删除商品 */
+const handleDeleteProduct = (index: number) => {
+  productList.value.splice(index, 1);
+};
+
+/** 提交表单 */
+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 || ''
+          }))
+        };
+
+        console.log('提交的数据:', submitData);
+        console.log('商品列表:', productList.value);
+
+        await updateContractsupply(submitData);
+        ElMessage.success('保存成功');
+        router.back();
+      } catch (e) {
+        console.error('保存失败:', e);
+        ElMessage.error('保存失败');
+      }
+    }
+  });
+};
+
+onMounted(() => {
+  getSupplierData();
+  getDetail();
+  getCompanyData();
+});
+</script>
+
+<style scoped>
+.app-container {
+  background: #f0f2f5;
+  min-height: 100vh;
+  padding: 0;
+}
+
+.detail-header {
+  background: #fff;
+  padding: 16px 24px;
+  display: flex;
+  align-items: center;
+  border-bottom: 1px solid #e8e8e8;
+  margin-bottom: 16px;
+}
+
+.back-icon {
+  font-size: 18px;
+  cursor: pointer;
+  margin-right: 12px;
+  color: #666;
+}
+
+.back-icon:hover {
+  color: #409eff;
+}
+
+.header-title {
+  font-weight: 500;
+  color: #333;
+}
+
+.form-card {
+  margin: 16px;
+  padding: 24px;
+}
+
+.large-form :deep(.el-form-item__label) {
+  font-weight: 500;
+}
+
+.form-text {
+  color: #303133;
+}
+
+.section-title {
+  margin: 20px 0;
+  padding-bottom: 12px;
+  border-bottom: 1px solid #e8e8e8;
+}
+</style>

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

@@ -0,0 +1,300 @@
+<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" label-width="100px">
+            <el-form-item label="协议单号" prop="contractSupplyNo">
+              <el-input v-model="queryParams.contractSupplyNo" placeholder="请输入协议单号" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+
+            <el-form-item label="供应商名称" prop="supplierName">
+              <el-input v-model="queryParams.supplierName" placeholder="供应商名称" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+
+            <el-form-item label="开始时间" prop="startTime">
+              <el-date-picker clearable
+                v-model="queryParams.startTime"
+                type="date"
+                value-format="YYYY-MM-DD"
+                placeholder="请选择开始时间"
+              />
+            </el-form-item>
+            <el-form-item label="结束时间" prop="endTime">
+              <el-date-picker clearable
+                v-model="queryParams.endTime"
+                type="date"
+                value-format="YYYY-MM-DD"
+                placeholder="请选择结束时间"
+              />
+            </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-button type="primary" icon="Plus" @click="handleAdd" v-hasPermi="['supplier:contractsupply:add']">新增</el-button>
+            </el-form-item>
+
+
+
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          协议供货信息列表
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" border :data="contractsupplyList">
+        <el-table-column label="主键ID" align="center" prop="id" v-if="false" />
+        <el-table-column label="协议单号" align="center" prop="contractSupplyNo" />
+        <el-table-column label="供应商编号" align="center" prop="supplyNo" />
+        <el-table-column label="供应商名称" align="left" prop="supplierName" min-width="220" />
+        <el-table-column label="商品数量" align="center" prop="productNumber" />
+        <el-table-column label="开始时间" align="center" prop="startTime" width="180">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="结束时间" align="center" prop="endTime" width="180">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="剩余时限(天)" align="center" prop="timeRemaining" />
+        <el-table-column label="状态" align="center" prop="status">
+          <template #default="scope">
+            <dict-tag :options="contract_supplier_status" :value="scope.row.status"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-button link type="primary" @click="handleAudit(scope.row)" v-hasPermi="['supplier:contractsupply:edit']">审核</el-button>
+            <el-divider direction="vertical" />
+            <el-button link type="primary" @click="handleUpdate(scope.row)" v-hasPermi="['supplier:contractsupply:edit']">编辑</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="getList" />
+    </el-card>
+    <!-- 添加或修改协议供货对话框 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
+      <el-form ref="contractsupplyFormRef" :model="form" :rules="rules" label-width="80px">
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 审核对话框 -->
+    <el-dialog title="审核" v-model="auditDialog.visible" width="500px" append-to-body>
+      <el-form ref="auditFormRef" :model="auditForm" :rules="auditRules" label-width="100px">
+        <el-form-item label="审核结果" prop="status">
+          <el-select v-model="auditForm.status" placeholder="请选择" style="width: 100%;">
+            <el-option label="生效" :value="1" />
+            <el-option label="驳回" :value="0" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitAudit">确 定</el-button>
+          <el-button @click="cancelAudit">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="Contractsupply" lang="ts">
+import { listContractsupply, getContractsupply, delContractsupply, addContractsupply, updateContractsupply, auditContractsupply } from '@/api/supplier/contractsupply';
+import { ContractsupplyVO, ContractsupplyQuery, ContractsupplyForm } from '@/api/supplier/contractsupply/types';
+import { useRouter } from 'vue-router';
+
+const router = useRouter();
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { contract_supplier_status } = toRefs<any>(proxy?.useDict('contract_supplier_status'));
+
+const contractsupplyList = ref<ContractsupplyVO[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const total = ref(0);
+
+const queryFormRef = ref<ElFormInstance>();
+const contractsupplyFormRef = ref<ElFormInstance>();
+const auditFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const auditDialog = reactive({
+  visible: false,
+  currentRow: null as ContractsupplyVO | null
+});
+
+const auditForm = ref({
+  status: undefined as number | undefined
+});
+
+const auditRules = {
+  status: [
+    { required: true, message: '请选择审核结果', trigger: 'change' }
+  ]
+};
+
+const initFormData: ContractsupplyForm = {
+}
+const data = reactive<PageData<ContractsupplyForm, ContractsupplyQuery>>({
+  form: {...initFormData},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    contractSupplyNo: undefined,
+    supplierName: undefined,
+    startTime: undefined,
+    endTime: undefined,
+    params: {
+    }
+  },
+  rules: {
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询协议供货列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listContractsupply(queryParams.value);
+  contractsupplyList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+}
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+}
+
+/** 表单重置 */
+const reset = () => {
+  form.value = {...initFormData};
+  contractsupplyFormRef.value?.resetFields();
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+}
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  router.push('/supplier/contractsupply/add');
+}
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: ContractsupplyVO) => {
+  const _id = row?.id;
+  if (!_id) {
+    proxy?.$modal.msgWarning('请选择要编辑的数据');
+    return;
+  }
+  router.push(`/supplier/contractsupply/edit/${_id}`);
+}
+
+/** 审核按钮操作 */
+const handleAudit = (row: ContractsupplyVO) => {
+  auditDialog.currentRow = row;
+  auditForm.value.status = undefined;
+  auditDialog.visible = true;
+};
+
+/** 提交审核 */
+const submitAudit = () => {
+  auditFormRef.value?.validate(async (valid: boolean) => {
+    if (valid && auditDialog.currentRow) {
+      try {
+        await auditContractsupply({
+          id: auditDialog.currentRow.id!,
+          status: auditForm.value.status!
+        });
+        proxy?.$modal.msgSuccess("审核成功");
+        auditDialog.visible = false;
+        await getList();
+      } catch (e) {
+        console.error('审核失败:', e);
+      }
+    }
+  });
+};
+
+/** 取消审核 */
+const cancelAudit = () => {
+  auditDialog.visible = false;
+  auditForm.value.status = undefined;
+};
+
+/** 提交按钮 */
+const submitForm = () => {
+  contractsupplyFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateContractsupply(form.value).finally(() =>  buttonLoading.value = false);
+      } else {
+        await addContractsupply(form.value).finally(() =>  buttonLoading.value = false);
+      }
+      proxy?.$modal.msgSuccess("操作成功");
+      dialog.visible = false;
+      await getList();
+    }
+  });
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: ContractsupplyVO) => {
+  const _ids = row?.id;
+  if (!_ids) {
+    proxy?.$modal.msgWarning('请选择要删除的数据');
+    return;
+  }
+  await proxy?.$modal.confirm('是否确认删除协议供货编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
+  await delContractsupply(_ids);
+  proxy?.$modal.msgSuccess("删除成功");
+  await getList();
+}
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download('supplier/contractsupply/export', {
+    ...queryParams.value
+  }, `contractsupply_${new Date().getTime()}.xlsx`)
+}
+
+onMounted(() => {
+  getList();
+});
+</script>
+
+<style scoped>
+
+</style>

+ 18 - 7
src/views/supplier/info/index.vue

@@ -161,7 +161,7 @@
         </el-form-item>
 
         <el-form-item label="开户行行号:" prop="bankNum">
-          <el-input v-model="paymentForm.bankNum" placeholder="请输入开户行行号" />
+          <el-input v-model="paymentForm.bankNum" placeholder="请输入开户行行号" maxlength="12" @input="onBankNumInput" />
         </el-form-item>
 
         <el-form-item label="开户行名称:" prop="bankInfoNo">
@@ -182,10 +182,10 @@
           <el-input v-model="paymentForm.businessAddress" placeholder="工商地址" disabled />
         </el-form-item>
 
-        <el-form-item label="是否主账号:" prop="num">
-          <el-radio-group v-model="paymentForm.num">
-            <el-radio :label="1">是</el-radio>
-            <el-radio :label="0">否</el-radio>
+        <el-form-item label="是否主账号:" prop="isture">
+          <el-radio-group v-model="paymentForm.isture">
+            <el-radio label="1">是</el-radio>
+            <el-radio label="0">否</el-radio>
           </el-radio-group>
         </el-form-item>
       </el-form>
@@ -682,6 +682,14 @@ const onContactPhoneInput = (val: string) => {
   }
 };
 
+// 开户行行号:仅允许输入数字,最多12位
+const onBankNumInput = (val: string) => {
+  const next = (val || '').replace(/\D+/g, '').slice(0, 12);
+  if (next !== paymentForm.value.bankNum) {
+    paymentForm.value.bankNum = next;
+  }
+};
+
 const handleBasicInfoSaved = async () => {
   const id = route.query.id as string;
   if (id) {
@@ -859,11 +867,14 @@ const systemBankList = ref<SystemBankVO[]>([]);
 // 付款信息表单验证规则
 const paymentFormRules = {
   invoiceTypeNo: [{ required: true, message: '请选择开票类型', trigger: 'change' }],
-  bankNum: [{ required: true, message: '请输入开户行行号', trigger: 'blur' }],
+  bankNum: [
+    { required: true, message: '请输入开户行行号', trigger: 'blur' },
+    { pattern: /^\d{12}$/, message: '开户行行号必须为12位数字', trigger: 'blur' }
+  ],
   bankInfoNo: [{ required: true, message: '请选择开户行名称', trigger: 'change' }],
   bankNo: [{ required: true, message: '请输入银行账户', trigger: 'blur' }],
   phone: [{ required: true, message: '请输入固定电话', trigger: 'blur' }],
-  num: [{ required: true, message: '请选择是否主账号', trigger: 'change' }]
+  isture: [{ required: true, message: '请选择是否主账号', trigger: 'change' }]
 };
 
 /** 获取发票类型列表 */