zlwu 10 месяцев назад
Родитель
Сommit
d0e8e68a6d

BIN
backend/.DS_Store


BIN
backend/ruoyi-modules/.DS_Store


BIN
backend/ruoyi-modules/ruoyi-xg/.DS_Store


+ 25 - 25
web/src/views/crm/contract/index.vue

@@ -2827,7 +2827,7 @@ const generateContractTemplate = (contractData: ContractVO | undefined): string
     return unitDict?.label || unitValue || '';
   };
 
-  // 构建产品明细表格行
+  // 构建产品明细表格行(使用 0.3px 边框)
   const productRows =
     contractData.products
       ?.map((product, index) => {
@@ -2837,19 +2837,19 @@ const generateContractTemplate = (contractData: ContractVO | undefined): string
 
         return `
     <tr>
-      <td style="padding: 4px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">${product.productName || ''}</td>
-      <td style="padding: 4px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">${product.model || product.productModel || ''}</td>
-      <td style="padding: 4px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">${getUnitLabel(product.unit)}</td>
-      <td style="padding: 4px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">${product.quantity || 0}</td>
-      <td style="padding: 4px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">${formatNumber(product.price)}</td>
-      <td style="padding: 4px 6px; border: 1px solid #000; text-align: right; font-size: 12px;">${formatNumber(beforeTax)}</td>
-      <td style="padding: 4px 6px; border: 1px solid #000; text-align: right; font-size: 12px;">${formatNumber(taxAmount)}</td>
-      <td style="padding: 4px 6px; border: 1px solid #000; text-align: right; font-weight: bold; font-size: 12px;">${formatNumber(subtotal)}</td>
-      <td style="padding: 4px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">${product.remark || ''}</td>
+      <td style="padding: 4px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px;">${product.productName || ''}</td>
+      <td style="padding: 4px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px;">${product.model || product.productModel || ''}</td>
+      <td style="padding: 4px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px;">${getUnitLabel(product.unit)}</td>
+      <td style="padding: 4px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px;">${product.quantity || 0}</td>
+      <td style="padding: 4px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px;">${formatNumber(product.price)}</td>
+      <td style="padding: 4px 6px; border: 0.3px solid #000; text-align: right; font-size: 12px;">${formatNumber(beforeTax)}</td>
+      <td style="padding: 4px 6px; border: 0.3px solid #000; text-align: right; font-size: 12px;">${formatNumber(taxAmount)}</td>
+      <td style="padding: 4px 6px; border: 0.3px solid #000; text-align: right; font-weight: bold; font-size: 12px;">${formatNumber(subtotal)}</td>
+      <td style="padding: 4px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px;">${product.remark || ''}</td>
     </tr>`;
       })
       .join('') ||
-    '<tr><td colspan="9" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">暂无产品信息</td></tr>';
+    '<tr><td colspan="9" style="padding: 5px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px;">暂无产品信息</td></tr>';
 
   return `
     <div style="width: 210mm; margin: 0 auto; padding: 0 15mm 20mm 15mm; font-family: '宋体', SimSun, serif; line-height: 1.6; box-sizing: border-box;">
@@ -2886,30 +2886,30 @@ const generateContractTemplate = (contractData: ContractVO | undefined): string
         <div style="font-weight: bold; margin-bottom: 10px; font-size: 12px; color: #000;">第一条、标的、数量、价格及其他</div>
 
         <!-- 产品表格 -->
-        <table style="width: 100%; border-collapse: collapse; margin-bottom: 0; border: 1px solid #000;">
+        <table style="width: 100%; border-collapse: collapse; margin-bottom: 0; border: 0.3px solid #000;">
           <thead>
             <tr style="background-color: #fff;">
-              <th style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px; width: 20%;">产品名称</th>
-              <th style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px; width: 10%;">规格</th>
-              <th style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px; width: 8%;">单位</th>
-              <th style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px; width: 8%;">用量</th>
-              <th style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px; width: 12%;">含税单价(元)</th>
-              <th style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px; width: 12%;">未含税金额(元)</th>
-              <th style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px; width: 10%;">税费(元)</th>
-              <th style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px; width: 13%;">含税金额(元)</th>
-              <th style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px; width: 15%;">备注</th>
+              <th style="padding: 5px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px; width: 20%;">产品名称</th>
+              <th style="padding: 5px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px; width: 10%;">规格</th>
+              <th style="padding: 5px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px; width: 8%;">单位</th>
+              <th style="padding: 5px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px; width: 8%;">用量</th>
+              <th style="padding: 5px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px; width: 12%;">含税单价(元)</th>
+              <th style="padding: 5px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px; width: 12%;">未含税金额(元)</th>
+              <th style="padding: 5px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px; width: 10%;">税费(元)</th>
+              <th style="padding: 5px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px; width: 13%;">含税金额(元)</th>
+              <th style="padding: 5px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px; width: 15%;">备注</th>
             </tr>
           </thead>
           <tbody>
             ${productRows}
             <!-- 合计行 -->
             <tr style="background-color: #fff; font-weight: bold;">
-              <td colspan="5" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">合计(人民币)</td>
-              <td colspan="4" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">${productTotalChinese}(¥${formatNumber(productTotal)})</td>
+              <td colspan="4" style="padding: 5px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px;">合计(人民币)</td>
+              <td colspan="5" style="padding: 5px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px;">${productTotalChinese}(¥${formatNumber(productTotal)})</td>
             </tr>
             <!-- 提示行 -->
             <tr>
-              <td colspan="9" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 11px; background-color: #fff;">
+              <td colspan="9" style="padding: 5px 6px; border: 0.3px solid #000; text-align: center; font-size: 11px; background-color: #fff;">
                 含税、含运费、装卸费(一票制)&nbsp;&nbsp;&nbsp;&nbsp;结算以实际到货吨位为准
               </td>
             </tr>
@@ -2981,7 +2981,7 @@ const generateContractTemplate = (contractData: ContractVO | undefined): string
           <div style="margin-bottom: 0;">开户银行:${contractData.partyABank || '中国建设银行安庆市城北支行'}</div>
           <div style="margin-bottom: 0;">账号:${contractData.partyAAccount || '34050168680800000121'}</div>
         </div>
-        <div style="flex: 1; padding-left: 10px; border: 1px solid #000; padding: 8px; line-height: 1.3; font-size: 12px; position: relative;">
+        <div style="flex: 1; padding-left: 10px; border: 1px solid #000; border-left: none; padding: 8px; line-height: 1.3; font-size: 12px; position: relative;">
           <div style="font-weight: bold; margin-bottom: 5px; padding-bottom: 5px; border-bottom: 1px solid #000;">需方</div>
           <!-- 印章图片:如果partyB是山峡公司或云栋公司,则显示印章(说明供需方已调换),根据公司名称决定显示哪个印章文件 -->
           ${contractData.partyB && (contractData.partyB.includes('山峡') || contractData.partyB.includes('安庆市山峡化工有限公司') || contractData.partyB.includes('云栋') || contractData.partyB.includes('安徽云栋化工有限公司')) ? `<img src="${getSealImageByCompanyName(contractData.partyB)}" alt="印章" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 140px; height: 140px; opacity: 0.85; z-index: 10;" />` : ''}

+ 5 - 2
web/src/views/crm/quote/detail.vue

@@ -108,7 +108,10 @@
             </el-table-column>
             <el-table-column label="包装规格" prop="packageSpec" min-width="150" :show-overflow-tooltip="true">
               <template #default="scope">
-                <span>{{ scope.row.packageSpec || '-' }}</span>
+                <span v-if="scope.row.packageSpec">
+                  <dict-tag :options="crm_product_gg" :value="scope.row.packageSpec" />
+                </span>
+                <span v-else>-</span>
               </template>
             </el-table-column>
             <el-table-column label="送货方式" prop="deliveryMethod" min-width="150" :show-overflow-tooltip="true">
@@ -384,7 +387,7 @@ const generateQuoteDialog = ref(false);
 const quoteContent = ref('');
 
 // 字典
-const { crm_quote_status, crm_product_unit } = toRefs<any>(proxy?.useDict('crm_quote_status', 'crm_product_unit'));
+const { crm_quote_status, crm_product_unit, crm_product_gg } = toRefs<any>(proxy?.useDict('crm_quote_status', 'crm_product_unit', 'crm_product_gg'));
 
 const quoteTypeMap: Record<string, string> = {
   sales: '销售',

+ 64 - 5
web/src/views/crm/quote/form.vue

@@ -131,9 +131,25 @@
                 <el-input v-model="scope.row.taxRate" placeholder="税率" @input="handleTaxRateChange(scope.row)" clearable />
               </template>
             </el-table-column>
-            <el-table-column label="单位" prop="unit" width="80" align="center">
+            <el-table-column label="单位" prop="unit" width="100" align="center">
               <template #default="scope">
-                <dict-tag :options="crm_product_unit" :value="scope.row.unit" />
+                <el-select
+                  v-model="scope.row.unit"
+                  placeholder="请选择单位"
+                  filterable
+                  allow-create
+                  default-first-option
+                  size="small"
+                  style="width: 100%"
+                  @change="(val) => handleUnitChange(scope.row, val)"
+                >
+                  <el-option
+                    v-for="dict in crm_product_unit"
+                    :key="dict.value"
+                    :label="dict.label"
+                    :value="dict.value"
+                  />
+                </el-select>
               </template>
             </el-table-column>
             <el-table-column prop="quantity" width="110">
@@ -162,7 +178,24 @@
             <el-table-column prop="packageSpec" min-width="150">
               <template #header> <span style="color: #f56c6c">*</span> 包装规格</template>
               <template #default="scope">
-                <el-input v-model="scope.row.packageSpec" placeholder="请输入包装规格" clearable size="small" />
+                <el-select
+                  v-model="scope.row.packageSpec"
+                  placeholder="请选择包装规格"
+                  filterable
+                  allow-create
+                  default-first-option
+                  clearable
+                  size="small"
+                  style="width: 100%"
+                  @change="(val) => handlePackageSpecChange(scope.row, val)"
+                >
+                  <el-option
+                    v-for="dict in crm_product_gg"
+                    :key="dict.value"
+                    :label="dict.label"
+                    :value="dict.value"
+                  />
+                </el-select>
               </template>
             </el-table-column>
             <el-table-column prop="deliveryMethod" min-width="150">
@@ -410,7 +443,7 @@ const route = useRoute();
 const { proxy } = getCurrentInstance() as any;
 
 // 字典数据
-const { crm_product_unit, crm_shfs } = toRefs<any>(proxy?.useDict('crm_product_unit', 'crm_shfs'));
+const { crm_product_unit, crm_shfs, crm_product_gg } = toRefs<any>(proxy?.useDict('crm_product_unit', 'crm_shfs', 'crm_product_gg'));
 
 const quoteTypeOptions = [
   { label: '销售', value: 'sales' },
@@ -468,7 +501,21 @@ const rules = ref({
   contactId: [{ required: true, message: '请选择联系人', trigger: 'change' }],
   quoteDate: [{ required: true, message: '请选择报价日期', trigger: 'change' }],
   validityDays: [{ required: true, message: '请输入有效期', trigger: 'blur' }],
-  freight: [{ type: 'number', min: 0, message: '运费需大于等于0', trigger: 'change' }],
+  freight: [{
+    validator: (_rule: any, value: any, callback: any) => {
+      if (value === null || value === undefined || value === '') {
+        callback();
+      } else {
+        const numValue = Number(value);
+        if (isNaN(numValue) || numValue < 0) {
+          callback(new Error('运费需大于等于0'));
+        } else {
+          callback();
+        }
+      }
+    },
+    trigger: 'change'
+  }],
   ownerUserId: [{ required: true, message: '请选择负责人', trigger: 'change' }],
   supplierId: [{ required: true, message: '请选择报价主体', trigger: 'change' }],
   quoteType: [{ required: true, message: '请选择报价类型', trigger: 'change' }]
@@ -630,6 +677,8 @@ const getQuoteInfo = async (id: string | number) => {
     if (!form.value.quoteType) {
       form.value.quoteType = 'sales';
     }
+    // 确保运费是数字类型(后端可能返回字符串或null)
+    form.value.freight = Number(form.value.freight) || 0;
 
     // 加载客户简称(从报价主题中提取,或从客户信息中获取)
     // 报价主题格式:供方简称-客户简称(报价单号)
@@ -915,6 +964,16 @@ const handleProductConfirm = (products: any[]) => {
   });
 };
 
+/** 单位变更处理(支持自由输入并写入字典) */
+const handleUnitChange = (product: QuoteProductForm, value: string) => {
+  product.unit = value;
+};
+
+/** 包装规格变更处理(支持自由输入并写入字典) */
+const handlePackageSpecChange = (product: QuoteProductForm, value: string) => {
+  product.packageSpec = value;
+};
+
 /** 单价变更 */
 const handlePriceChange = (product: QuoteProductForm) => {
   calculateProductAmount(product);

+ 6 - 3
web/src/views/crm/quote/index.vue

@@ -583,7 +583,10 @@
               </el-table-column>
               <el-table-column label="包装规格" prop="packageSpec" min-width="150" :show-overflow-tooltip="true">
                 <template #default="scope">
-                  <span>{{ scope.row.packageSpec || '-' }}</span>
+                  <span v-if="scope.row.packageSpec">
+                    <dict-tag :options="crm_product_gg" :value="scope.row.packageSpec" />
+                  </span>
+                  <span v-else>-</span>
                 </template>
               </el-table-column>
               <el-table-column label="送货方式" prop="deliveryMethod" min-width="150" :show-overflow-tooltip="true">
@@ -840,7 +843,7 @@ const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const userStore = useUserStore();
 
 // 字典数据
-const { crm_quote_status, crm_product_unit } = toRefs<any>(proxy!.useDict('crm_quote_status', 'crm_product_unit'));
+const { crm_quote_status, crm_product_unit, crm_product_gg } = toRefs<any>(proxy!.useDict('crm_quote_status', 'crm_product_unit', 'crm_product_gg'));
 
 const quoteTypeMap: Record<string, { label: string; tag: 'success' | 'warning' }> = {
   sales: { label: '销售', tag: 'success' },
@@ -1721,7 +1724,7 @@ const generateQuoteTemplate = (quote: QuoteVO, supplierInfo?: SupplierInfoVO): s
                 送货方式
                 ${
                   sealImg
-                    ? `<img src="${sealImg}" alt="印章" style="position: absolute; left: 50%; top: calc(50% - 50px); transform: translate(-50%, -50%); width: 120px; height: 120px; z-index: 10; pointer-events: none; opacity: 0.9;" />`
+                    ? `<img src="${sealImg}" alt="印章" style="position: absolute; left: 50%; top: calc(50% - 50px); transform: translate(-50%, -50%); width: 120px; max-width: 120px; max-height: 120px; height: auto; object-fit: contain; z-index: 10; pointer-events: none; opacity: 0.9;" />`
                     : ''
                 }
               </th>