zlwu 1 год назад
Родитель
Сommit
6066c4f5b1
20 измененных файлов с 447 добавлено и 296 удалено
  1. 6 0
      backend/ruoyi-modules/ruoyi-crm/pom.xml
  2. 8 1
      backend/ruoyi-modules/ruoyi-crm/src/main/java/org/dromara/crm/controller/CrmOpportunityController.java
  3. 20 0
      backend/ruoyi-modules/ruoyi-crm/src/main/java/org/dromara/crm/domain/CrmOpportunityStageLog.java
  4. 5 0
      backend/ruoyi-modules/ruoyi-crm/src/main/java/org/dromara/crm/domain/bo/CrmQuoteBo.java
  5. 24 0
      backend/ruoyi-modules/ruoyi-crm/src/main/java/org/dromara/crm/domain/vo/CrmOpportunityStageLogVo.java
  6. 5 0
      backend/ruoyi-modules/ruoyi-crm/src/main/java/org/dromara/crm/domain/vo/CrmQuoteVo.java
  7. 6 4
      backend/ruoyi-modules/ruoyi-crm/src/main/java/org/dromara/crm/service/ICrmOpportunityService.java
  8. 71 6
      backend/ruoyi-modules/ruoyi-crm/src/main/java/org/dromara/crm/service/impl/CrmOpportunityServiceImpl.java
  9. 25 0
      backend/ruoyi-modules/ruoyi-crm/src/main/java/org/dromara/crm/service/impl/CrmQuoteServiceImpl.java
  10. 15 0
      sql/add_opportunity_stage_log_fields.sql
  11. 21 1
      web/src/api/crm/opportunity/types.ts
  12. 1 0
      web/src/api/crm/quote.ts
  13. BIN
      web/src/assets/images/logo.gif
  14. 1 2
      web/src/views/crm/opportunity/components/ChangeStageDialog.vue
  15. 5 23
      web/src/views/crm/opportunity/components/MarkLostDialog.vue
  16. 6 62
      web/src/views/crm/opportunity/components/MarkWonDialog.vue
  17. 28 9
      web/src/views/crm/opportunity/detail.vue
  18. 53 45
      web/src/views/crm/quote/detail.vue
  19. 2 2
      web/src/views/crm/quote/form.vue
  20. 145 141
      web/src/views/crm/quote/index.vue

+ 6 - 0
backend/ruoyi-modules/ruoyi-crm/pom.xml

@@ -71,6 +71,12 @@
             <artifactId>ruoyi-common-oss</artifactId>
         </dependency>
 
+        <!-- 系统模块(用于查询用户信息) -->
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-system</artifactId>
+        </dependency>
+
         <!-- Sa-Token 权限认证 -->
         <dependency>
             <groupId>cn.dev33</groupId>

+ 8 - 1
backend/ruoyi-modules/ruoyi-crm/src/main/java/org/dromara/crm/controller/CrmOpportunityController.java

@@ -131,7 +131,8 @@ public class CrmOpportunityController extends BaseController {
     @PutMapping("/lost")
     public R<Void> markLost(@RequestBody MarkLostRequest request) {
         return toAjax(opportunityService.markLost(request.getId(),
-                request.getLostReason(), request.getRemark()));
+                request.getLostReason(), request.getRemark(),
+                request.getCompetitorInfo(), request.getImprovement()));
     }
 
     /**
@@ -320,6 +321,12 @@ public class CrmOpportunityController extends BaseController {
         private String lostReason;
 
         private String remark;
+
+        /** 竞争对手信息 */
+        private String competitorInfo;
+
+        /** 改进建议 */
+        private String improvement;
     }
 
     /**

+ 20 - 0
backend/ruoyi-modules/ruoyi-crm/src/main/java/org/dromara/crm/domain/CrmOpportunityStageLog.java

@@ -83,6 +83,26 @@ public class CrmOpportunityStageLog extends TenantEntity {
      */
     private String remark;
 
+    /**
+     * 输单详细说明
+     */
+    private String lostDetail;
+
+    /**
+     * 竞争对手信息
+     */
+    private String competitorInfo;
+
+    /**
+     * 改进建议
+     */
+    private String improvement;
+
+    /**
+     * 赢单说明
+     */
+    private String wonRemark;
+
     /**
      * 删除标志(0存在 1删除)
      */

+ 5 - 0
backend/ruoyi-modules/ruoyi-crm/src/main/java/org/dromara/crm/domain/bo/CrmQuoteBo.java

@@ -166,4 +166,9 @@ public class CrmQuoteBo extends BaseEntity {
     @NotEmpty(message = "产品列表不能为空", groups = { AddGroup.class })
     private List<CrmQuoteProductBo> products;
 
+    /**
+     * 审批人ID(用于查询某个用户已审批过的报价单)
+     */
+    private Long approvedByUserId;
+
 }

+ 24 - 0
backend/ruoyi-modules/ruoyi-crm/src/main/java/org/dromara/crm/domain/vo/CrmOpportunityStageLogVo.java

@@ -115,4 +115,28 @@ public class CrmOpportunityStageLogVo implements Serializable {
     @ExcelProperty(value = "备注")
     private String remark;
 
+    /**
+     * 输单详细说明
+     */
+    @ExcelProperty(value = "输单详细说明")
+    private String lostDetail;
+
+    /**
+     * 竞争对手信息
+     */
+    @ExcelProperty(value = "竞争对手信息")
+    private String competitorInfo;
+
+    /**
+     * 改进建议
+     */
+    @ExcelProperty(value = "改进建议")
+    private String improvement;
+
+    /**
+     * 赢单说明
+     */
+    @ExcelProperty(value = "赢单说明")
+    private String wonRemark;
+
 }

+ 5 - 0
backend/ruoyi-modules/ruoyi-crm/src/main/java/org/dromara/crm/domain/vo/CrmQuoteVo.java

@@ -203,6 +203,11 @@ public class CrmQuoteVo implements Serializable {
     @ExcelProperty(value = "负责人")
     private String ownerUserName;
 
+    /**
+     * 负责人手机号
+     */
+    private String ownerUserMobile;
+
     /**
      * 关联合同ID
      */

+ 6 - 4
backend/ruoyi-modules/ruoyi-crm/src/main/java/org/dromara/crm/service/ICrmOpportunityService.java

@@ -86,12 +86,14 @@ public interface ICrmOpportunityService {
         /**
          * 标记输单
          *
-         * @param id         商机ID
-         * @param lostReason 输单原因
-         * @param remark     备注说明
+         * @param id             商机ID
+         * @param lostReason     输单原因
+         * @param remark         输单详细说明
+         * @param competitorInfo 竞争对手信息
+         * @param improvement    改进建议
          * @return 是否成功
          */
-        Boolean markLost(Long id, String lostReason, String remark);
+        Boolean markLost(Long id, String lostReason, String remark, String competitorInfo, String improvement);
 
         /**
          * 商机统计概览

+ 71 - 6
backend/ruoyi-modules/ruoyi-crm/src/main/java/org/dromara/crm/service/impl/CrmOpportunityServiceImpl.java

@@ -429,8 +429,8 @@ public class CrmOpportunityServiceImpl implements ICrmOpportunityService {
         // entity.setStageChangeTime(LocalDateTime.now());
         baseMapper.updateById(entity);
 
-        // 记录阶段变更日志
-        recordStageLog(id, oldStage, "won", 100, BigDecimal.valueOf(100), "标记赢单", remark);
+        // 记录阶段变更日志(赢单)
+        recordWonStageLog(id, oldStage, 100, BigDecimal.valueOf(100), remark);
 
         // 更新客户成交状态
         if (entity.getCustomerId() != null) {
@@ -442,7 +442,7 @@ public class CrmOpportunityServiceImpl implements ICrmOpportunityService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public Boolean markLost(Long id, String lostReason, String remark) {
+    public Boolean markLost(Long id, String lostReason, String remark, String competitorInfo, String improvement) {
         CrmOpportunity entity = baseMapper.selectById(id);
         if (entity == null) {
             throw new ServiceException("商机不存在");
@@ -464,9 +464,8 @@ public class CrmOpportunityServiceImpl implements ICrmOpportunityService {
         // entity.setStageChangeTime(LocalDateTime.now());
         baseMapper.updateById(entity);
 
-        // 记录阶段变更日志
-        recordStageLog(id, oldStage, "lost", 0, BigDecimal.ZERO, "标记输单",
-                lostReason + (StringUtils.isNotBlank(remark) ? " - " + remark : ""));
+        // 记录阶段变更日志(输单)
+        recordLostStageLog(id, oldStage, 0, BigDecimal.ZERO, lostReason, remark, competitorInfo, improvement);
 
         return true;
     }
@@ -631,6 +630,72 @@ public class CrmOpportunityServiceImpl implements ICrmOpportunityService {
         stageLogMapper.insert(log);
     }
 
+    /**
+     * 记录输单阶段变更日志
+     */
+    private void recordLostStageLog(Long opportunityId, String oldStage,
+            Integer stagePercent, BigDecimal winRate,
+            String lostReason, String lostDetail, String competitorInfo, String improvement) {
+        CrmOpportunityStageLog log = new CrmOpportunityStageLog();
+        log.setOpportunityId(opportunityId);
+
+        // 设置阶段信息
+        log.setFromStage(oldStage);
+        log.setToStage("lost");
+        log.setToPercent(stagePercent);
+
+        // 设置变更类型
+        log.setChangeType("lost");
+
+        // 设置变更原因为输单原因
+        log.setChangeReason(lostReason);
+
+        // 设置输单相关详细信息
+        log.setLostDetail(lostDetail); // 输单详细说明
+        log.setCompetitorInfo(competitorInfo); // 竞争对手信息
+        log.setImprovement(improvement); // 改进建议
+
+        // 获取商机金额
+        CrmOpportunity opportunity = baseMapper.selectById(opportunityId);
+        if (opportunity != null) {
+            log.setChangeAmount(opportunity.getAmount());
+        }
+
+        stageLogMapper.insert(log);
+    }
+
+    /**
+     * 记录赢单阶段变更日志
+     */
+    private void recordWonStageLog(Long opportunityId, String oldStage,
+            Integer stagePercent, BigDecimal winRate,
+            String wonRemark) {
+        CrmOpportunityStageLog log = new CrmOpportunityStageLog();
+        log.setOpportunityId(opportunityId);
+
+        // 设置阶段信息
+        log.setFromStage(oldStage);
+        log.setToStage("won");
+        log.setToPercent(stagePercent);
+
+        // 设置变更类型
+        log.setChangeType("won");
+
+        // 设置变更原因
+        log.setChangeReason("标记赢单");
+
+        // 设置赢单说明
+        log.setWonRemark(wonRemark);
+
+        // 获取商机金额
+        CrmOpportunity opportunity = baseMapper.selectById(opportunityId);
+        if (opportunity != null) {
+            log.setChangeAmount(opportunity.getAmount());
+        }
+
+        stageLogMapper.insert(log);
+    }
+
     /**
      * 更新客户成交状态
      */

+ 25 - 0
backend/ruoyi-modules/ruoyi-crm/src/main/java/org/dromara/crm/service/impl/CrmQuoteServiceImpl.java

@@ -26,6 +26,8 @@ import org.dromara.crm.service.ICrmQuoteApprovalService;
 import org.dromara.crm.service.ICrmQuotePdfService;
 import org.dromara.crm.service.ICrmQuoteProductService;
 import org.dromara.crm.service.ICrmQuoteService;
+import org.dromara.system.service.ISysUserService;
+import org.dromara.system.domain.vo.SysUserVo;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -55,6 +57,7 @@ public class CrmQuoteServiceImpl implements ICrmQuoteService {
     private final ICrmContractService contractService;
     private final ICrmOpportunityService opportunityService;
     private final ICrmContactService contactService;
+    private final ISysUserService userService;
 
     /**
      * 构建查询条件
@@ -76,6 +79,16 @@ public class CrmQuoteServiceImpl implements ICrmQuoteService {
         lqw.le(bo.getParams().get("quoteDateEnd") != null, CrmQuote::getQuoteDate, bo.getParams().get("quoteDateEnd"));
         lqw.between(bo.getParams().get("beginTime") != null && bo.getParams().get("endTime") != null,
                 CrmQuote::getCreateTime, bo.getParams().get("beginTime"), bo.getParams().get("endTime"));
+
+        // 查询某个用户已审批过的报价单
+        if (bo.getApprovedByUserId() != null) {
+            lqw.inSql(CrmQuote::getId,
+                    "SELECT DISTINCT quote_id FROM crm_quote_approval " +
+                            "WHERE approver_id = " + bo.getApprovedByUserId() +
+                            " AND approval_result IN ('approved', 'rejected') " +
+                            " AND del_flag = 0");
+        }
+
         lqw.orderByDesc(CrmQuote::getCreateTime);
         return lqw;
     }
@@ -154,6 +167,18 @@ public class CrmQuoteServiceImpl implements ICrmQuoteService {
                     log.warn("查询联系人信息失败, contactId: {}", vo.getContactId(), e);
                 }
             }
+
+            // 填充负责人手机号
+            if (vo.getOwnerUserId() != null && StringUtils.isBlank(vo.getOwnerUserMobile())) {
+                try {
+                    SysUserVo user = userService.selectUserById(vo.getOwnerUserId());
+                    if (user != null && StringUtils.isNotBlank(user.getPhonenumber())) {
+                        vo.setOwnerUserMobile(user.getPhonenumber());
+                    }
+                } catch (Exception e) {
+                    log.warn("查询负责人手机号失败, ownerUserId: {}", vo.getOwnerUserId(), e);
+                }
+            }
         }
         return vo;
     }

+ 15 - 0
sql/add_opportunity_stage_log_fields.sql

@@ -0,0 +1,15 @@
+-- 为商机阶段变更日志表添加输单和赢单的详细信息字段
+-- 适用于 MySQL 数据库
+
+-- 添加输单详细说明字段
+ALTER TABLE `crm_opportunity_stage_log` ADD COLUMN `lost_detail` VARCHAR(2000) NULL COMMENT '输单详细说明' AFTER `remark`;
+
+-- 添加竞争对手信息字段
+ALTER TABLE `crm_opportunity_stage_log` ADD COLUMN `competitor_info` VARCHAR(1000) NULL COMMENT '竞争对手信息' AFTER `lost_detail`;
+
+-- 添加改进建议字段
+ALTER TABLE `crm_opportunity_stage_log` ADD COLUMN `improvement` VARCHAR(1000) NULL COMMENT '改进建议' AFTER `competitor_info`;
+
+-- 添加赢单说明字段
+ALTER TABLE `crm_opportunity_stage_log` ADD COLUMN `won_remark` VARCHAR(2000) NULL COMMENT '赢单说明' AFTER `improvement`;
+

+ 21 - 1
web/src/api/crm/opportunity/types.ts

@@ -351,6 +351,22 @@ export interface OpportunityStageLogVO {
   changeTime: string
   /** 备注 */
   remark?: string
+  /** 输单详细说明 */
+  lostDetail?: string
+  /** 竞争对手信息 */
+  competitorInfo?: string
+  /** 改进建议 */
+  improvement?: string
+  /** 赢单说明 */
+  wonRemark?: string
+  /** 原阶段(前端展示用字段名,映射fromStage) */
+  fromStage?: string
+  /** 新阶段(前端展示用字段名,映射toStage) */
+  toStage?: string
+  /** 创建人姓名 */
+  createByName?: string
+  /** 创建时间 */
+  createTime?: string
 }
 
 /**
@@ -405,8 +421,12 @@ export interface OpportunityMarkLostParams {
   id: number | string
   /** 输单原因 */
   lostReason: string
-  /** 备注说明 */
+  /** 输单详细说明 */
   remark?: string
+  /** 竞争对手信息 */
+  competitorInfo?: string
+  /** 改进建议 */
+  improvement?: string
 }
 
 /**

+ 1 - 0
web/src/api/crm/quote.ts

@@ -13,6 +13,7 @@ export interface QuoteQuery {
   opportunityId?: number;
   status?: string;
   ownerUserId?: number;
+  approvedByUserId?: number; // 审批人ID(用于查询某个用户已审批过的报价单)
   minAmount?: number;
   maxAmount?: number;
   isLatest?: number;

BIN
web/src/assets/images/logo.gif


+ 1 - 2
web/src/views/crm/opportunity/components/ChangeStageDialog.vue

@@ -17,8 +17,7 @@
       <el-descriptions-item label="当前进度">
         <el-progress :percentage="opportunity?.stagePercent" :color="getProgressColor(opportunity?.stagePercent)" />
       </el-descriptions-item>
-      <el-descriptions-item label="当前赢单率">{{ opportunity?.winRate }}%</el-descriptions-item>
-      <el-descriptions-item label="预估金额">¥{{ formatMoney(opportunity?.amount) }}</el-descriptions-item>
+      <el-descriptions-item label="当前赢单率" :span="2">{{ opportunity?.winRate }}%</el-descriptions-item>
     </el-descriptions>
 
     <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">

+ 5 - 23
web/src/views/crm/opportunity/components/MarkLostDialog.vue

@@ -15,11 +15,8 @@
       <el-descriptions-item label="当前阶段">
         <el-tag :type="getStageType(opportunity?.stage)">{{ getStageText(opportunity?.stage) }}</el-tag>
       </el-descriptions-item>
-      <el-descriptions-item label="预估金额">
-        <span style="color: #f56c6c; font-weight: bold">¥{{ formatMoney(opportunity?.amount) }}</span>
-      </el-descriptions-item>
-      <el-descriptions-item label="跟进次数">{{ opportunity?.followCount || 0 }}次</el-descriptions-item>
       <el-descriptions-item label="负责人">{{ opportunity?.ownerUserName || '-' }}</el-descriptions-item>
+      <el-descriptions-item label="跟进次数" :span="2">{{ opportunity?.followCount || 0 }}次</el-descriptions-item>
     </el-descriptions>
 
     <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
@@ -38,27 +35,15 @@
       </el-form-item>
 
       <el-form-item label="输单详细说明" prop="remark">
-        <el-input v-model="form.remark" type="textarea" :rows="6" :placeholder="remarkPlaceholder" maxlength="1000" show-word-limit />
+        <el-input v-model="form.remark" type="textarea" :rows="6" :placeholder="remarkPlaceholder" />
       </el-form-item>
 
       <el-form-item label="竞争对手信息" prop="competitorInfo">
-        <el-input
-          v-model="form.competitorInfo"
-          type="textarea"
-          :rows="3"
-          placeholder="请输入竞争对手信息(选填,如:竞争对手名称、优势、价格等)"
-          maxlength="500"
-        />
+        <el-input v-model="form.competitorInfo" type="textarea" :rows="3" placeholder="请输入竞争对手信息(选填,如:竞争对手名称、优势、价格等)" />
       </el-form-item>
 
       <el-form-item label="改进建议" prop="improvement">
-        <el-input
-          v-model="form.improvement"
-          type="textarea"
-          :rows="3"
-          placeholder="请输入改进建议(选填,如:产品改进、服务优化、价格策略等)"
-          maxlength="500"
-        />
+        <el-input v-model="form.improvement" type="textarea" :rows="3" placeholder="请输入改进建议(选填,如:产品改进、服务优化、价格策略等)" />
       </el-form-item>
     </el-form>
 
@@ -108,10 +93,7 @@ const form = ref<OpportunityMarkLostParams>({
 // 表单校验规则
 const rules = ref({
   lostReason: [{ required: true, message: '请选择输单原因', trigger: 'change' }],
-  remark: [
-    { required: true, message: '请输入输单详细说明', trigger: 'blur' },
-    { min: 20, max: 1000, message: '长度在 20 到 1000 个字符', trigger: 'blur' }
-  ]
+  remark: [{ required: true, message: '请输入输单详细说明', trigger: 'blur' }]
 });
 
 const formRef = ref<InstanceType<typeof ElForm>>();

+ 6 - 62
web/src/views/crm/opportunity/components/MarkWonDialog.vue

@@ -15,11 +15,8 @@
       <el-descriptions-item label="当前阶段">
         <el-tag :type="getStageType(opportunity?.stage)">{{ getStageText(opportunity?.stage) }}</el-tag>
       </el-descriptions-item>
-      <el-descriptions-item label="预估金额">
-        <span style="color: #f56c6c; font-weight: bold">¥{{ formatMoney(opportunity?.amount) }}</span>
-      </el-descriptions-item>
-      <el-descriptions-item label="预估赢单率">{{ opportunity?.winRate }}%</el-descriptions-item>
       <el-descriptions-item label="预计成交日期">{{ opportunity?.expectDealDate || '-' }}</el-descriptions-item>
+      <el-descriptions-item label="预估赢单率" :span="2">{{ opportunity?.winRate }}%</el-descriptions-item>
     </el-descriptions>
 
     <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
@@ -34,58 +31,9 @@
         />
       </el-form-item>
 
-      <el-form-item label="实际成交金额" prop="actualAmount">
-        <el-input v-model="form.actualAmount" placeholder="请输入实际成交金额" type="number" :min="0" style="width: 100%">
-          <template #prepend>¥</template>
-        </el-input>
-        <div v-if="getDifference()" style="margin-top: 8px; font-size: 12px; color: #909399">
-          与预估金额差异:
-          <span :style="{ color: getDifference() > 0 ? '#67c23a' : '#f56c6c', fontWeight: 'bold' }">
-            {{ getDifference() > 0 ? '+' : '' }}¥{{ formatMoney(Math.abs(getDifference())) }} ({{ getDifferencePercent() }}%)
-          </span>
-        </div>
-      </el-form-item>
-
       <el-form-item label="赢单说明" prop="remark">
-        <el-input
-          v-model="form.remark"
-          type="textarea"
-          :rows="5"
-          placeholder="请输入赢单说明(如:成交过程、客户反馈、成功因素等)"
-          maxlength="1000"
-          show-word-limit
-        />
+        <el-input v-model="form.remark" type="textarea" :rows="5" placeholder="请输入赢单说明(如:成交过程、客户反馈、成功因素等)" />
       </el-form-item>
-
-      <!-- 后续操作 -->
-      <el-divider content-position="left">后续操作</el-divider>
-
-      <el-form-item label="创建合同">
-        <el-checkbox v-model="form.createContract">立即创建合同草稿</el-checkbox>
-        <div style="color: #909399; font-size: 12px; margin-top: 4px">勾选后将自动创建合同草稿,关联商机信息和产品明细</div>
-      </el-form-item>
-
-      <!-- 如果勾选创建合同,显示合同信息 -->
-      <template v-if="form.createContract">
-        <el-form-item label="合同名称" prop="contractName">
-          <el-input v-model="form.contractName" placeholder="请输入合同名称" maxlength="200" style="width: 100%" />
-          <div style="color: #909399; font-size: 12px; margin-top: 4px">建议格式:客户名称-产品-合同</div>
-        </el-form-item>
-
-        <el-alert type="info" :closable="false" style="margin-bottom: 16px">
-          <template #title>
-            <div style="font-size: 13px">
-              <div style="margin-bottom: 4px; font-weight: 600">合同自动创建说明:</div>
-              <ul style="margin: 0; padding-left: 20px">
-                <li>合同将自动关联商机和客户信息</li>
-                <li>商机的产品明细将自动复制到合同</li>
-                <li>合同状态为草稿,需完善后提交审批</li>
-                <li>合同金额默认为实际成交金额</li>
-              </ul>
-            </div>
-          </template>
-        </el-alert>
-      </template>
     </el-form>
 
     <template #footer>
@@ -129,7 +77,7 @@ const submitLoading = ref(false);
 const form = ref({
   id: undefined as any,
   actualDealDate: '',
-  actualAmount: undefined,
+  actualAmount: 0, // 默认为0,隐藏该字段
   remark: '',
   createContract: false, // 是否创建合同
   contractName: '' // 合同名称
@@ -139,11 +87,7 @@ const form = ref({
 const rules = computed(() => {
   const baseRules: any = {
     actualDealDate: [{ required: true, message: '请选择实际成交日期', trigger: 'change' }],
-    actualAmount: [{ required: true, message: '请输入实际成交金额', trigger: 'blur' }],
-    remark: [
-      { required: true, message: '请输入赢单说明', trigger: 'blur' },
-      { min: 10, max: 1000, message: '长度在 10 到 1000 个字符', trigger: 'blur' }
-    ]
+    remark: [{ required: true, message: '请输入赢单说明', trigger: 'blur' }]
   };
 
   // 如果勾选创建合同,合同名称必填
@@ -190,7 +134,7 @@ const initForm = () => {
   form.value = {
     id: props.opportunity.id,
     actualDealDate: dateStr,
-    actualAmount: props.opportunity.amount, // 默认与预估金额相同
+    actualAmount: 0, // 默认为0
     remark: '',
     createContract: false,
     contractName: suggestedContractName // 默认合同名称
@@ -293,7 +237,7 @@ const resetForm = () => {
   form.value = {
     id: undefined as any,
     actualDealDate: '',
-    actualAmount: undefined,
+    actualAmount: 0,
     remark: '',
     createContract: false,
     contractName: ''

+ 28 - 9
web/src/views/crm/opportunity/detail.vue

@@ -39,15 +39,6 @@
               :disabled="opportunityInfo?.status !== 'following'"
               >添加跟进</el-button
             >
-            <el-button
-              type="success"
-              size="small"
-              icon="DocumentCopy"
-              @click="handleConvertToContract"
-              v-hasPermi="['crm:contract:add']"
-              v-if="opportunityInfo?.status === 'won'"
-              >转为合同</el-button
-            >
           </div>
         </div>
       </template>
@@ -276,6 +267,34 @@
                   <!-- 变更原因 -->
                   <p v-if="item.changeReason" style="margin-top: 10px"><strong>变更原因:</strong>{{ item.changeReason }}</p>
 
+                  <!-- 输单详细信息 -->
+                  <div
+                    v-if="item.toStage === 'lost'"
+                    style="margin-top: 16px; padding: 12px; background-color: #fef0f0; border-left: 3px solid #f56c6c; border-radius: 4px"
+                  >
+                    <div v-if="item.lostDetail" style="margin-bottom: 10px">
+                      <strong style="color: #f56c6c">输单详细说明:</strong>
+                      <div style="margin-top: 4px; color: #303133">{{ item.lostDetail }}</div>
+                    </div>
+                    <div v-if="item.competitorInfo" style="margin-bottom: 10px">
+                      <strong style="color: #f56c6c">竞争对手信息:</strong>
+                      <div style="margin-top: 4px; color: #303133">{{ item.competitorInfo }}</div>
+                    </div>
+                    <div v-if="item.improvement">
+                      <strong style="color: #f56c6c">改进建议:</strong>
+                      <div style="margin-top: 4px; color: #303133">{{ item.improvement }}</div>
+                    </div>
+                  </div>
+
+                  <!-- 赢单说明 -->
+                  <div
+                    v-if="item.toStage === 'won' && item.wonRemark"
+                    style="margin-top: 16px; padding: 12px; background-color: #f0f9ff; border-left: 3px solid #67c23a; border-radius: 4px"
+                  >
+                    <strong style="color: #67c23a">赢单说明:</strong>
+                    <div style="margin-top: 4px; color: #303133">{{ item.wonRemark }}</div>
+                  </div>
+
                   <!-- 变更人和时间 -->
                   <div style="margin-top: 12px; display: flex; justify-content: space-between; align-items: center">
                     <span style="color: #909399; font-size: 12px">

+ 53 - 45
web/src/views/crm/quote/detail.vue

@@ -69,51 +69,8 @@
             </el-descriptions-item>
           </el-descriptions>
 
-          <!-- 报价条款 -->
-          <el-divider content-position="left">报价条款</el-divider>
-          <el-descriptions :column="1" border>
-            <el-descriptions-item label="付款条款">{{ quote?.paymentTerms }}</el-descriptions-item>
-            <el-descriptions-item label="交付条款">{{ quote?.deliveryTerms }}</el-descriptions-item>
-            <el-descriptions-item label="备注">{{ quote?.remark }}</el-descriptions-item>
-          </el-descriptions>
-
-          <!-- 附件 -->
-          <el-divider content-position="left" v-if="attachmentList.length > 0">附件</el-divider>
-          <div v-if="attachmentList.length > 0" class="attachment-section">
-            <div class="attachment-header">
-              <el-icon><Paperclip /></el-icon>
-              <span class="attachment-title">附件 ({{ attachmentList.length }})</span>
-            </div>
-            <div class="attachment-list">
-              <div v-for="(file, index) in attachmentList" :key="index" class="upload-file-card">
-                <!-- 缩略图区域 -->
-                <div class="upload-file-thumb">
-                  <ImagePreview v-if="isImageFile(file)" :src="file.ossId" class="upload-thumb-image" :isOssId="true" />
-                  <div v-else class="upload-file-placeholder">
-                    <el-icon><Document /></el-icon>
-                    <span class="file-ext">{{ getFileExt(file.name) }}</span>
-                  </div>
-                </div>
-
-                <!-- 文件名称 -->
-                <div class="upload-file-name" :title="file.name">
-                  {{ getShortFileName(file.name) }}
-                </div>
-
-                <!-- 下载按钮 -->
-                <div class="upload-file-card-actions">
-                  <el-button type="success" size="small" @click.stop="handleDownloadFile(file)">
-                    <el-icon><Download /></el-icon>
-                    下载
-                  </el-button>
-                </div>
-              </div>
-            </div>
-          </div>
-        </el-tab-pane>
-
-        <!-- 产品明细 -->
-        <el-tab-pane label="产品明细" name="products">
+          <!-- 产品明细 -->
+          <el-divider content-position="left">产品明细</el-divider>
           <el-table :data="quote?.productList" border stripe>
             <el-table-column type="index" label="序号" width="60" align="center" />
             <el-table-column label="产品名称" prop="productName" min-width="120" :show-overflow-tooltip="true" />
@@ -179,6 +136,48 @@
               </el-col>
             </el-row>
           </div>
+
+          <!-- 报价条款 -->
+          <el-divider content-position="left">报价条款</el-divider>
+          <el-descriptions :column="1" border>
+            <el-descriptions-item label="付款条款" v-if="false">{{ quote?.paymentTerms }}</el-descriptions-item>
+            <el-descriptions-item label="交付条款" v-if="false">{{ quote?.deliveryTerms }}</el-descriptions-item>
+            <el-descriptions-item label="备注">{{ quote?.remark }}</el-descriptions-item>
+          </el-descriptions>
+
+          <!-- 附件 -->
+          <el-divider content-position="left" v-if="attachmentList.length > 0">附件</el-divider>
+          <div v-if="attachmentList.length > 0" class="attachment-section">
+            <div class="attachment-header">
+              <el-icon><Paperclip /></el-icon>
+              <span class="attachment-title">附件 ({{ attachmentList.length }})</span>
+            </div>
+            <div class="attachment-list">
+              <div v-for="(file, index) in attachmentList" :key="index" class="upload-file-card">
+                <!-- 缩略图区域 -->
+                <div class="upload-file-thumb">
+                  <ImagePreview v-if="isImageFile(file)" :src="file.ossId" class="upload-thumb-image" :isOssId="true" />
+                  <div v-else class="upload-file-placeholder">
+                    <el-icon><Document /></el-icon>
+                    <span class="file-ext">{{ getFileExt(file.name) }}</span>
+                  </div>
+                </div>
+
+                <!-- 文件名称 -->
+                <div class="upload-file-name" :title="file.name">
+                  {{ getShortFileName(file.name) }}
+                </div>
+
+                <!-- 下载按钮 -->
+                <div class="upload-file-card-actions">
+                  <el-button type="success" size="small" @click.stop="handleDownloadFile(file)">
+                    <el-icon><Download /></el-icon>
+                    下载
+                  </el-button>
+                </div>
+              </div>
+            </div>
+          </div>
         </el-tab-pane>
 
         <!-- 审批流程 -->
@@ -706,6 +705,11 @@ onMounted(() => {
   color: #e6a23c;
 }
 
+.text-warning {
+  color: #e6a23c;
+  font-weight: 500;
+}
+
 .final-amount-text {
   color: #f56c6c;
   font-size: 18px;
@@ -718,6 +722,10 @@ onMounted(() => {
   border-radius: 4px;
 }
 
+.mt-4 {
+  margin-top: 16px;
+}
+
 .version-header {
   display: flex;
   justify-content: space-between;

+ 2 - 2
web/src/views/crm/quote/form.vue

@@ -176,7 +176,7 @@
           </span>
         </el-divider>
         <div>
-          <el-row :gutter="20">
+          <el-row :gutter="20" v-if="false">
             <el-col :span="12">
               <el-form-item label="付款条款" prop="paymentTerms">
                 <el-input v-model="form.paymentTerms" type="textarea" :rows="3" placeholder="请输入付款条款" maxlength="500" show-word-limit />
@@ -388,7 +388,7 @@ const form = ref<QuoteForm>({
   validityDays: 30,
   ownerUserId: undefined,
   ownerUserName: '',
-  remark: '',
+  remark: '1. 以上报价含税13%、含运费 ,款到发货 ,最终价格须盖章回传。\n2. 结算以实际到货数量为准,价格随行就市 。',
   products: []
 });
 

+ 145 - 141
web/src/views/crm/quote/index.vue

@@ -1,42 +1,37 @@
 <template>
   <div class="app-container">
-    <!-- 数据统计 -->
+    <!-- 视图切换标签 -->
+    <el-row class="mb-3">
+      <el-radio-group v-model="activeView" @change="handleViewChange">
+        <el-radio-button value="my">我的报价</el-radio-button>
+        <el-radio-button value="all">全部报价</el-radio-button>
+        <el-radio-button value="submitted">待审批</el-radio-button>
+        <el-radio-button value="accepted">已审批</el-radio-button>
+        <!-- <el-radio-button value="sent">已发送</el-radio-button> -->
+        <!-- <el-radio-button value="expired">已过期</el-radio-button> -->
+      </el-radio-group>
+    </el-row>
+
+    <!-- 当前视图统计数据 -->
     <el-row :gutter="20" class="mb-3">
-      <el-col :span="6">
-        <el-statistic title="总数" :value="statistics.total || 0">
-          <template #suffix>单</template>
-        </el-statistic>
-      </el-col>
-      <el-col :span="6">
-        <el-statistic title="本月新增" :value="statistics.monthNew || 0">
+      <el-col :span="8">
+        <el-statistic :title="getViewTitle() + '总数'" :value="currentViewStatistics.total || 0">
           <template #suffix>单</template>
         </el-statistic>
       </el-col>
-      <el-col :span="6">
-        <el-statistic title="待审批" :value="statistics.pending || 0">
+      <el-col :span="8">
+        <el-statistic title="当前页草稿" :value="currentViewStatistics.draft || 0">
           <template #suffix>单</template>
         </el-statistic>
       </el-col>
-      <el-col :span="6">
-        <el-statistic title="总金额" :value="statistics.totalAmount || 0" :precision="2">
+      <el-col :span="8">
+        <el-statistic title="当前页金额" :value="currentViewStatistics.totalAmount || 0" :precision="2">
           <template #prefix>¥</template>
           <template #suffix>万</template>
         </el-statistic>
       </el-col>
     </el-row>
 
-    <!-- 视图切换标签 -->
-    <el-row class="mb-3">
-      <el-radio-group v-model="activeView" @change="handleViewChange">
-        <el-radio-button value="my">我的报价</el-radio-button>
-        <el-radio-button value="all">全部报价</el-radio-button>
-        <el-radio-button value="submitted">待审批</el-radio-button>
-        <!-- <el-radio-button value="sent">已发送</el-radio-button> -->
-        <!-- <el-radio-button value="accepted">已接受</el-radio-button> -->
-        <!-- <el-radio-button value="expired">已过期</el-radio-button> -->
-      </el-radio-group>
-    </el-row>
-
     <!-- 查询表单 -->
     <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="80px">
       <el-form-item label="报价单号" prop="quoteNo">
@@ -380,7 +375,6 @@ import {
   generatePdf,
   generateShare,
   createNewVersion,
-  statisticsOverview,
   approveQuote,
   listApproval,
   type QuoteQuery,
@@ -392,6 +386,7 @@ import { listUser } from '@/api/system/user';
 import type { UserVO } from '@/api/system/user/types';
 import { useUserStore } from '@/store/modules/user';
 import TinyMceEditor from '@/components/TinyMceEditor/index.vue';
+import logoImg from '@/assets/images/logo.gif';
 
 const router = useRouter();
 const { copy, copied, isSupported } = useClipboard();
@@ -426,35 +421,46 @@ const dateRange = ref<string[]>([]);
 // 视图切换
 const activeView = ref('my');
 
-// 统计数据
-const statistics = ref({
+// 当前视图统计数据
+const currentViewStatistics = ref({
   total: 0,
-  monthNew: 0,
-  pending: 0,
+  draft: 0,
   totalAmount: 0
 });
 
-/** 加载统计数据 */
-const loadStatistics = async () => {
-  try {
-    const res = await statisticsOverview();
-    const data = res.data || {};
-    statistics.value = {
-      total: data.totalCount || 0,
-      monthNew: data.draftCount || 0, // 这里可以根据实际需求调整
-      pending: data.submittedCount || 0,
-      totalAmount: data.totalAmount ? data.totalAmount / 10000 : 0 // 转换为万元
-    };
-  } catch (error) {
-    console.error('加载统计数据失败:', error);
-    // 发生错误时使用默认值
-    statistics.value = {
-      total: 0,
-      monthNew: 0,
-      pending: 0,
-      totalAmount: 0
-    };
-  }
+/** 获取视图标题 */
+const getViewTitle = () => {
+  const titles: Record<string, string> = {
+    my: '我的报价',
+    all: '全部报价',
+    submitted: '待审批',
+    accepted: '已审批'
+  };
+  return titles[activeView.value] || '';
+};
+
+/** 计算当前视图统计数据 */
+const calculateCurrentViewStatistics = () => {
+  // 基于当前列表数据计算统计
+  const list = quoteList.value || [];
+
+  // 总数
+  const totalCount = total.value;
+
+  // 草稿状态数量
+  const draftCount = list.filter((item) => item.status === 'draft').length;
+
+  // 计算总金额(单位:元,需转换为万元)
+  const totalAmountValue = list.reduce((sum, item) => {
+    const amount = parseFloat(String(item.finalAmount || 0));
+    return sum + (isNaN(amount) ? 0 : amount);
+  }, 0);
+
+  currentViewStatistics.value = {
+    total: totalCount,
+    draft: draftCount,
+    totalAmount: totalAmountValue / 10000 // 转换为万元
+  };
 };
 
 // 提交审批对话框
@@ -557,23 +563,33 @@ const getList = async () => {
 
     // 根据视图设置状态过滤
     if (activeView.value === 'my') {
-      // TODO: 添加当前用户过滤
-      queryParams.value.ownerUserId = undefined; // LoginHelper.getUserId()
+      // 只查询当前用户负责的报价单
+      queryParams.value.ownerUserId = userStore.userId as number;
+      queryParams.value.status = undefined;
+      queryParams.value.approvedByUserId = undefined;
+    } else if (activeView.value === 'accepted') {
+      // 查询当前用户已审批过的报价单(包括审批通过和审批拒绝)
+      queryParams.value.ownerUserId = undefined;
+      queryParams.value.status = undefined;
+      queryParams.value.approvedByUserId = userStore.userId as number;
     } else if (activeView.value !== 'all') {
+      // 按状态查询(如待审批)
+      queryParams.value.ownerUserId = undefined;
       queryParams.value.status = activeView.value;
+      queryParams.value.approvedByUserId = undefined;
     } else {
-      // 如果没有手动选择状态,则清空状态过滤
-      if (activeView.value === 'all') {
-        queryParams.value.status = undefined;
-      }
+      // 查看全部报价单,清空所有过滤条件
+      queryParams.value.ownerUserId = undefined;
+      queryParams.value.status = undefined;
+      queryParams.value.approvedByUserId = undefined;
     }
 
     const res = await listQuote(queryParams.value);
     quoteList.value = res.rows;
     total.value = res.total;
 
-    // 加载统计数据
-    loadStatistics();
+    // 计算当前视图统计数据
+    calculateCurrentViewStatistics();
   } finally {
     loading.value = false;
   }
@@ -950,115 +966,103 @@ const generateQuoteTemplate = (quote: QuoteVO): string => {
       return sum + (isNaN(subtotal) ? 0 : subtotal);
     }, 0) || 0;
 
+  // 格式化日期(从创建时间获取日期)
+  const formatDate = (dateStr: string): string => {
+    if (!dateStr) return '';
+    const date = new Date(dateStr);
+    const year = date.getFullYear();
+    const month = String(date.getMonth() + 1).padStart(2, '0');
+    const day = String(date.getDate()).padStart(2, '0');
+    return `${year}/${month}/${day}`;
+  };
+
   // 构建产品明细表格行
   const productRows =
     quote.productList
       ?.map(
-        (product) => `
+        (product, index) => `
     <tr>
-      <td style="padding: 8px; border: 1px solid #ddd;">${product.productName || ''}</td>
-      <td style="padding: 8px; border: 1px solid #ddd; text-align: center;">${product.productNo || ''}</td>
-      <td style="padding: 8px; border: 1px solid #ddd; text-align: right;">${formatNumber(product.price)}</td>
-      <td style="padding: 8px; border: 1px solid #ddd; text-align: right;">${formatNumber(product.price)}</td>
-      <td style="padding: 8px; border: 1px solid #ddd; text-align: center;">${product.quantity || 0}</td>
-      <td style="padding: 8px; border: 1px solid #ddd; text-align: center;">${formatNumber(product.discount, 0)}</td>
-      <td style="padding: 8px; border: 1px solid #ddd; text-align: right; font-weight: bold;">${formatNumber(product.subtotal)}</td>
+      <td style="padding: 8px; border: 1px solid #000; text-align: center;">${index + 1}</td>
+      <td style="padding: 8px; border: 1px solid #000;">${product.productName || ''}</td>
+      <td style="padding: 8px; border: 1px solid #000; text-align: center;">${product.model || ''}</td>
+      <td style="padding: 8px; border: 1px solid #000; text-align: center;">${product.unit || ''}</td>
+      <td style="padding: 8px; border: 1px solid #000; text-align: center;">${product.quantity || 0}</td>
+      <td style="padding: 8px; border: 1px solid #000; text-align: right;">${formatNumber(product.price)}</td>
+      <td style="padding: 8px; border: 1px solid #000; text-align: right;">${formatNumber(product.taxAmount)}</td>
+      <td style="padding: 8px; border: 1px solid #000; text-align: right; font-weight: bold;">${formatNumber(product.subtotal)}</td>
+      <td style="padding: 8px; border: 1px solid #000; text-align: center;">${product.packageSpec || ''}</td>
+      <td style="padding: 8px; border: 1px solid #000; text-align: center;">${product.deliveryMethod || '自提'}</td>
     </tr>
   `
       )
-      .join('') || '<tr><td colspan="7" style="padding: 8px; text-align: center;">暂无产品信息</td></tr>';
+      .join('') || '<tr><td colspan="10" style="padding: 8px; border: 1px solid #000; text-align: center;">暂无产品信息</td></tr>';
 
   return `
-    <div style="max-width: 900px; margin: 0 auto; padding: 20px; font-family: Arial, '微软雅黑', sans-serif; position: relative; min-height: 800px;">
+    <div style="max-width: 1200px; margin: 0 auto; padding: 20px; font-family: Arial, '微软雅黑', sans-serif;">
+      <!-- Logo和标题 -->
+      <div style="text-align: right; margin-bottom: 20px;">
+        <img src="${logoImg}" alt="山峡化工" style="height: 60px; vertical-align: middle;" />
+      </div>
+
       <!-- 标题 -->
-      <h1 style="text-align: center; font-size: 28px; margin-bottom: 10px;">${quote.customerName}</h1>
-      <h2 style="text-align: center; font-size: 24px; margin-bottom: 30px;">报价单</h2>
+      <h1 style="text-align: center; font-size: 32px; margin-bottom: 50px; font-weight: bold;">山峡销售报价单</h1>
 
       <!-- 报价单基本信息 -->
-      <table style="width: 100%; border-collapse: collapse; margin-bottom: 20px; border: 1px solid #ddd;">
-        <tr>
-          <td style="padding: 8px; border: 1px solid #ddd; width: 25%; background-color: #f5f5f5;"><strong>报价单编号:</strong></td>
-          <td style="padding: 8px; border: 1px solid #ddd; width: 25%;">${quote.quoteNo}</td>
-          <td style="padding: 8px; border: 1px solid #ddd; width: 25%; background-color: #f5f5f5;"><strong>报价日期:</strong></td>
-          <td style="padding: 8px; border: 1px solid #ddd; width: 25%;">${quote.quoteDate}</td>
-        </tr>
-        <tr>
-          <td style="padding: 8px; border: 1px solid #ddd; background-color: #f5f5f5;"><strong>报价人:</strong></td>
-          <td style="padding: 8px; border: 1px solid #ddd;">${quote.ownerUserName || ''}</td>
-          <td style="padding: 8px; border: 1px solid #ddd; background-color: #f5f5f5;"><strong>报价人手机号:</strong></td>
-          <td style="padding: 8px; border: 1px solid #ddd;">${quote.contactMobile || ''}</td>
-        </tr>
-      </table>
-
-      <!-- 客户信息 -->
-      <h3 style="font-size: 18px; margin-top: 30px; margin-bottom: 15px; border-bottom: 2px solid #333; padding-bottom: 5px;">客户信息:</h3>
-      <table style="width: 100%; margin-bottom: 20px;">
-        <tr>
-          <td style="padding: 5px;"><strong>客户名称:</strong>${quote.customerName}</td>
-        </tr>
-        <tr>
-          <td style="padding: 5px;"><strong>电话:</strong>${quote.contactMobile || ''}</td>
-          <td style="padding: 5px;"><strong>邮箱:</strong>${quote.contactEmail || ''}</td>
-        </tr>
-      </table>
-
-      <!-- 报价说明 -->
-      <h3 style="font-size: 18px; margin-top: 30px; margin-bottom: 15px; border-bottom: 2px solid #333; padding-bottom: 5px;">报价说明:</h3>
-      <p style="line-height: 1.8; margin-bottom: 20px;">${quote.quoteTitle || ''}</p>
-      ${(quote as any).description ? `<p style="line-height: 1.8; margin-bottom: 20px;">${(quote as any).description}</p>` : ''}
+      <div style="margin-bottom: 40px; font-size: 16px; line-height: 2.5; display: flex; justify-content: center;">
+        <div style="width: 90%; max-width: 1000px;">
+          <div style="display: flex; justify-content: space-between; margin-bottom: 10px;">
+            <div style="flex: 1; text-align: left;">日&nbsp;&nbsp;期:&nbsp;&nbsp;${formatDate(quote.createTime)}</div>
+            <div style="flex: 1; text-align: left;">编&nbsp;&nbsp;号:&nbsp;&nbsp;${quote.quoteNo}</div>
+          </div>
+          <div style="display: flex; justify-content: space-between; margin-bottom: 10px;">
+            <div style="flex: 1; text-align: left;">需&nbsp;&nbsp;方:&nbsp;&nbsp;${quote.customerName}</div>
+            <div style="flex: 1; text-align: left;">供&nbsp;&nbsp;方:&nbsp;&nbsp;安庆市山峡化工有限公司</div>
+          </div>
+          <div style="display: flex; justify-content: space-between;">
+            <div style="flex: 1; text-align: left;">联系人:&nbsp;&nbsp;${quote.contactName || ''}</div>
+            <div style="flex: 1; text-align: left;">联系人:&nbsp;&nbsp;${quote.ownerUserName || ''} / ${(quote as any).ownerUserMobile || ''}</div>
+          </div>
+        </div>
+      </div>
 
-      <!-- 报价单详情 -->
-      <h3 style="font-size: 18px; margin-top: 30px; margin-bottom: 15px; border-bottom: 2px solid #333; padding-bottom: 5px;">报价单详情:</h3>
-      <table style="width: 100%; border-collapse: collapse; margin-bottom: 20px; border: 1px solid #ddd;">
+      <!-- 产品列表、备注、合计(整合为一个表格) -->
+      <table style="width: 100%; border-collapse: collapse; margin-bottom: 20px; border: 2px solid #000;">
         <thead>
-          <tr style="background-color: #f5f5f5;">
-            <th style="padding: 10px; border: 1px solid #ddd; text-align: left;">产品名称</th>
-            <th style="padding: 10px; border: 1px solid #ddd; text-align: center;">产品编码</th>
-            <th style="padding: 10px; border: 1px solid #ddd; text-align: center;">价格</th>
-            <th style="padding: 10px; border: 1px solid #ddd; text-align: center;">售价</th>
-            <th style="padding: 10px; border: 1px solid #ddd; text-align: center;">数量</th>
-            <th style="padding: 10px; border: 1px solid #ddd; text-align: center;">折扣</th>
-            <th style="padding: 10px; border: 1px solid #ddd; text-align: center;">合计</th>
+          <tr style="background-color: #fff;">
+            <th style="padding: 10px; border: 1px solid #000; text-align: center; width: 5%;">序号</th>
+            <th style="padding: 10px; border: 1px solid #000; text-align: center; width: 15%;">物料名称</th>
+            <th style="padding: 10px; border: 1px solid #000; text-align: center; width: 8%;">规格</th>
+            <th style="padding: 10px; border: 1px solid #000; text-align: center; width: 6%;">单位</th>
+            <th style="padding: 10px; border: 1px solid #000; text-align: center; width: 8%;">数量</th>
+            <th style="padding: 10px; border: 1px solid #000; text-align: center; width: 10%;">含税单价</th>
+            <th style="padding: 10px; border: 1px solid #000; text-align: center; width: 10%;">税额/元</th>
+            <th style="padding: 10px; border: 1px solid #000; text-align: center; width: 10%;">含税总价/元</th>
+            <th style="padding: 10px; border: 1px solid #000; text-align: center; width: 14%;">包装规格</th>
+            <th style="padding: 10px; border: 1px solid #000; text-align: center; width: 14%;">送货方式</th>
           </tr>
         </thead>
         <tbody>
           ${productRows}
+          <!-- 备注行 -->
+          <tr>
+            <td colspan="10" style="padding: 10px; border: 1px solid #000; background-color: #f9f9f9; text-align: center;">
+              备注:桶收押金25kg 桶15元/ 个 | 吨桶 450元/ 个
+            </td>
+          </tr>
+          <!-- 合计行 -->
+          <tr>
+            <td colspan="2" style="padding: 10px; border: 1px solid #000; background-color: #fff; text-align: center;"><strong>合计(元):</strong></td>
+            <td colspan="8" style="padding: 10px; border: 1px solid #000; text-align: right; font-weight: bold; font-size: 18px;">${formatNumber(productTotal)}</td>
+          </tr>
         </tbody>
       </table>
 
-      <!-- 整单折扣和总金额 -->
-      <div style="margin-bottom: 30px;">
-        <p style="line-height: 1.8;">整单折扣(%):${formatNumber(quote.discountRate, 2)} &nbsp;&nbsp;&nbsp;&nbsp; 产品总金额(元):${productTotal.toFixed(2)}</p>
-      </div>
-
-      <!-- 客户确认 -->
-      <h3 style="font-size: 18px; margin-top: 30px; margin-bottom: 15px; border-bottom: 2px solid #333; padding-bottom: 5px;">客户确认:</h3>
-      <table style="width: 100%; margin-bottom: 40px;">
-        <tr>
-          <td style="padding: 5px; width: 50%;"><strong>客户签名:</strong></td>
-          <td style="padding: 5px; width: 50%;"><strong>客户签名/盖章:</strong></td>
-        </tr>
-        <tr>
-          <td style="padding: 50px 5px; position: relative; height: 100px;">
-            <div style="position: relative; height: 100%; border: 1px dashed #ddd; background: #fafafa; text-align: center; line-height: 100px; color: #999;">
-              签名区域
-            </div>
-          </td>
-          <td style="padding: 50px 5px; position: relative; height: 100px;">
-            <div style="position: relative; height: 100%; border: 1px dashed #ddd; background: #fafafa; text-align: center; line-height: 100px; color: #999;">
-              盖章区域(点击此处插入图片)
-            </div>
-          </td>
-        </tr>
-      </table>
-
       <!-- 备注信息 -->
       ${
         quote.remark
           ? `
-      <div style="margin-top: 30px; padding: 15px; background-color: #f9f9f9; border-left: 4px solid #409eff;">
-        <strong>备注:</strong><br/>
-        <p style="margin-top: 10px; line-height: 1.8;">${quote.remark}</p>
+      <div style="margin-top: 30px; padding: 15px;">
+        <p style="margin: 0; line-height: 1.8; white-space: pre-wrap;">${quote.remark.replace(/\n/g, '<br/>')}</p>
       </div>
       `
           : ''